Open In App

Hibernate – Interceptors

Last Updated : 24 Mar, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

Interceptors are used in conjunction with Java EE managed classes to allow developers to invoke interceptor methods on an associated target class, in conjunction with method invocations or lifecycle events. Common uses of interceptors are logging, auditing, and profiling.

The Interceptors 1.1 specification is part of the final release of JSR 318, Enterprise JavaBeans 3.1, available from http://jcp.org/en/jsr/detail?id=318.

An interceptor can be defined within a target class as an interceptor method, or in an associated class called an interceptor class. Interceptor classes contain methods that are invoked in conjunction with the methods or lifecycle events of the target class.

Interceptor Classes: Interceptor classes may be designated with the optional javax.interceptor.Interceptor annotation, but interceptor classes aren’t required to be so annotated. An interceptor class must have a public, no-argument constructor.

The target class can have any number of interceptor classes associated with it. The order in which the interceptor classes are invoked is determined by the order in which the interceptor classes are defined in the javax.interceptor.Interceptors annotation. However, this order can be overridden in the deployment descriptor.

Interceptor classes may be targets of dependency injection. Dependency injection occurs when the interceptor class instance is created, using the naming context of the associated target class, and before any @PostConstruct callbacks are invoked.

Interceptor Lifecycle: Interceptor classes have the same lifecycle as their associated target class. When a target class instance is created, an interceptor class instance is also created for each declared interceptor class in the target class. That is, if the target class declares multiple interceptor classes, an instance of each class is created when the target class instance is created. The target class instance and all interceptor class instances are fully instantiated before any @PostConstruct callbacks are invoked, and any @PreDestroy callbacks are invoked before the target class and interceptor class instances are destroyed.

Interceptors and CDI: Contexts and Dependency Injection for the Java EE Platform (CDI) build on the basic functionality of Java EE interceptors. For information on CDI interceptors, including a discussion of interceptor binding types, see Using Interceptors in CDI Applications. 

Here we will be looking at various ways of intercepting operations within Hibernate’s abstracted relational mapping implementation.

Hibernate Interceptors 

The Hibernate Interceptor is an interface that allows us to react to certain events within Hibernate. These interceptors are registered as callbacks and provide communication links between Hibernate’s session and application. With such a callback, an application can intercept core Hibernate’s operations such as save, update, delete, etc.

Types of Interceptors

There are two ways of defining interceptors:

  1. implementing the org.hibernate.Interceptor interface
  2. extending the org.hibernate.EmptyInterceptor class

2.1: Implementing an Interceptor Interface: Implementing org.hibernate.Interceptor requires implementing about 14 accompanying methods. These methods include onLoad, onSave, onDelete, findDirty, and a few more. It is also important to ensure that any class that implements Interceptor interface is serializable (implements java.io.Serializable).

A typical example would look as follows:

// Class 
public class CustomInterceptorImpl
implements Interceptor, Serializable {

    // Annotation 
    @Override 
    // Method 
    public boolean onLoad(Object entity, Serializable id, Object[] state,
                          String[] propertyNames, Type[] types) 
            throws CallbackException {
            
               // ... return false; }
               // ... @Override public String onPrepareStatement(String sql)
               {
                 // ... return sql; }
               }

If there are no special requirements, extending the EmptyInterceptor class and only overriding the required methods is highly recommended.

2.2: Extending EmptyInterceptor: Extending the org.hibernate.EmptyInterceptor class provides an easier way of defining an interceptor. We now only need to override the methods that relate to the operation we want to intercept.

For example, we can define our CustomInterceptor as:

public class CustomInterceptor extends EmptyInterceptor { }

And if we need to intercept data saving operations before they are executed, we need to override onSave method:

// Annotation 
@Override
// Method 
public boolean onSave(Object entity, Serializable id, Object[] state,
                      String[] propertyNames, Type[] types) {

    if (entity instanceof User) {

        logger.info(((User) entity).toString());
    }
    
    return super.onSave(entity, id, state, propertyNames, types);
}

Notice how this implementation simply prints out the entity – if it’s a User. While it’s possible to return a value of true or false, it’s a good practice to allow propagation of onSave event by invoking super.onSave(). Another use case would be providing an audit trail for database interactions. We can use the onFlushDirty() method to know when an entity changes.

For the User object, we can decide to update its lastModified date property whenever changes on entities of type User happen.

In the below method we will be illustrating how this can be achieved which is as follows:

// Annotation

@Override

// Method

public boolean onFlushDirty(Object entity, Serializable id,

                            Object[] currentState, Object [] previousState,

                            String[] propertyNames, Type[] types) {

    if (entity instanceof User) {

        ((User) entity).setLastModified(new Date());

        logger.info(((User) entity).toString());

    }

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

}

Other events such as delete and load (object initialization) can be intercepted by implementing the corresponding onDelete and onLoad methods respectively.

Registering Interceptors

A Hibernate interceptor can either be registered as Session-scoped or SessionFactory-scoped.

3.1: Session-scoped Interceptor: A Session-scoped interceptor is linked to a specific session. It’s created when the session is being defined or opened as:

public static Session getSessionWithInterceptor(Interceptor interceptor)

throws IOException {

    return getSessionFactory().withOptions() .interceptor(interceptor).openSession();

}

In the above, we explicitly registered an interceptor with a particular hibernate session.

3.2: SessionFactory-scoped Interceptor: A SessionFactory-scoped interceptor is registered before building a SessionFactory. This is typically done through the applyInterceptor method on a SessionFactoryBuilder instance:

ServiceRegistry serviceRegistry = configureServiceRegistry(); 

SessionFactory sessionFactory = getSessionFactoryBuilder(serviceRegistry).applyInterceptor(new CustomInterceptor()) .build();

It’s important to note that a SessionFactory-scoped interceptor will be applied to all sessions. Hence, we need to be careful not to store session-specific states as this interceptor will be used by different sessions concurrently.

  • For a session-specific behavior, it’s recommended to explicitly open a session with a different interceptor as earlier shown.
  • For SessionFactory-scoped interceptors, we naturally need to ensure that it’s thread-safe. This can be achieved by specifying a session context in the properties file:

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

Or by adding this to our XML configuration file:

<property name=”hibernate.current_session_context_class”> 

org.hibernate.context.internal.ThreadLocalSessionContext 

</property>

Also, to ensure serializability, SessionFactory-scoped interceptors must implement the readResolve method of the Serializable interface.

Conclusion: We’ve seen how to define and register Hibernate interceptors either as Session-scoped or SessionFactory-scoped. In either case, we must ensure that the interceptors are serializable especially if we want a serializable session.

Other alternatives to interceptors include Hibernate Events and JPA Callbacks. And, as always, you can check out the complete source code over on Github.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads