Tuesday, February 23, 2010

Hibernate Interceptors

Introduction

This article mainly talks about hibernate interceptor and its uses. This also includes different types of hibernate interceptors and available APIs.

Sometimes we may require performing some operations before or after performing core functional logic. In such case an interceptor can be used to intercept the existing functionality to provide new features or add-ons to the application. Interceptors provide callback APIs which can be called on specific event or action.

Hibernate Interceptors

The Interceptor interface provides callbacks from the session to the application, allowing the application to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded. One possible use for this is to track auditing information. For example, Interceptor can be used to set audit trail properties such as created by, created on, updated by and updated on automatically.

Types of Hibernate Interceptors

1. Application scope interceptors
2. Session scope interceptors

1. Application Scope Interceptors
An application may contain more than one database session. If the interceptor is configured in the application scope level, then it is applicable to persistent objects in all the sessions of that application. The following code configures application scope interceptor.

Configuration configuration = new Configuration();
configuration.setInterceptor(new AppScopeInterceptor());
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session1 = sessionFactory.openSession();
Session session2 = sessionFactory.openSession();

Hibernate Configuration class provides an API to set application scope interceptors. In the above example the interceptor is applicable to both “session1” and “session2”.

2. Session Scope Interceptors
Each session can have different interceptors configured to them. In that case the interceptor is applicable to persistent objects of that particular session.

Configuration configuration = new Configuration();
SessionFactory sessionFactory = configuration.buildSessionFactory();
FirstInterceptor firstInterceptor = new FirstInterceptor ();
Session session1 = sessionFactory.openSession(firstInterceptor);
SecondInterceptor secondInterceptor = new SecondInterceptor ();
Session session2 = sessionFactory.openSession(secondInterceptor);

In the above example two different interceptors (firstInterceptor, secondInterceptor) are configured to two different sessions (session1, session2). In that case “firstInterceptor” is applicable only for “session1” and the “secondInterceptor” for “session2”.

Interceptor API
1. Interceptor – interface
2. EmptyInterceptor - contains empty method implementation of interceptor interface.

We can either implement interceptor directly or extend EmptyInterceptor. It’s always better to extend EmptyInterceptor and override only methods that are required.

This interface allows application to provide greater customization to persist the objects. It even allows the code to modify the state of the persistent object. It has more than 15 different methods and so the designers of Hibernate provide the concrete EmptyInterceptor class which implements the Interceptor interface to provide default/empty method implementations. Applications can use EmptyInterceptor class instead of depending on the Interceptor interface.

Examples

1. Set audit trail properties

For example almost in every application the database tables might have some kind of audit trail fields. Ideally these fields need to be set based on who created/updated and when. There fields may present in almost all table at times. Using Hibernate Interceptor this can be set without touching the existing application code. So this helps the developer from setting these fields each and every time manually and places this code in a common place apart from application code.

For Example we have a base class which contains only the audit trail information.

Public class BaseEntity implements Serializable {
String createdBy
Date createdOn
String updatedBy
Date updatedOn
}

And we have PersonEntity extending BaseEntity.
Public Class PersonEntity extends BaseEntity {
String fname;
String lname;
String fullName;
}

To implement the interceptor, we need to implement the Interceptor interface or extend the EmptyIntercepter. Let’s extend EmptyInterceptor so we need to implement only the methods we actually need.

Public class AuditTrailInterceptor extends EmptyInterceptor {
public boolean onFlushDirty(
Object entity, Serializable id, Object[] currentState,
Object[] previousState, String[] propertyNames, Type[] types) {
setValue(currentState, propertyNames, "updatedBy", UserUtils.getCurrentUsername());
setValue(currentState, propertyNames, "updatedOn", new Date());
return true;
}


public boolean onSave(
Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) {
setValue(state, propertyNames, "createdBy", UserUtils.getCurrentUsername());
setValue(state, propertyNames, "createdOn", new Date());
return true;
}


private void setValue(
Object[] currentState, String[] propertyNames,
String propertyToSet, Object value) {
def index = propertyNames.toList().indexOf(propertyToSet)
if (index >= 0) {
currentState[index] = value
}
}
}

Here we implemented onFlushDirty() and onSave() methods because these are called for SQL updates and inserts, respectively. For example when a new PersonEntity is created onSave method is called and here we need to set “createdBy” and “createdOn” fields. Same way when an existing PersonEntity is modified onFlushDirty method is called and here we need to set “updatedBy” and “updatedOn”.

There are two ways to modify / view values of persistent objects. Notice that each method takes parameters of “state” array and “propertyNames” array. State array contains values of a persistent object where as propertyNames array contains properties of a persistent object.

For example “propertyNames” array of a PersonEntity might look like this
{
“createBy”,
“createdOn”,
“updatedBy”,
“updatedOn”,
“fname”,
“lname”,
“fullName”
}
And “state” array might look like this after creating and updating the same
{
“Tom”,
07-10-2009 14:27:00,
“Jerry”,
17-10-2009 14:30:00,
“xxxx”,
“yyyy”,
“xxxx yyyy”
}

One way of modifying the persistent objects are to traverse through these arrays and edit the appropriate value as we have done in “setValue” utility method.

The other way of accessing and modifying the persistent objects are through entity parameter. As specified below:-
public boolean onSave(
Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
if (entity instanceof BaseEntity){
BaseEntity baseEntity = (BaseEntity)entity;
baseEntity.setCreatedBy(UserUtils.getCurrentUsername());
baseEntity.setCreatedOn(new Date());
}
return super.onSave(entity, id, state, propertyNames, types);
}

2. Set Derived properties
Any property is derived from existing properties can also be set through interceptors. For example in the above PersonEntity the fullName can be derived from fname and lname properties. Instead of setting this value in the application logic we can move it to interceptor.

public boolean onSave(
Object entity, Serializable id, Object[] state, String[] propertyNames,
Type[] types){
if (entity instanceof PersonEntity){
PersonEntity personEntity = (PersonEntity)entity;
String fullName = personEntity.getFName() + " " +
personEntity.getLName();
personEntity.setFullName(fullName);
}
return super.onSave(entity, id, state, propertyNames, types);
}

3. Maintain log information
Log information can also be recorded on save or update.
public class LoggerInterceptor extends EmptyInterceptor{
public boolean onSave(
Object entity, Serializable id, Object[] state, String[] propertyNames,
Type[] types){
System.out.println("Inserting the persistent Object " +
entity.getClass() + " with Id " + id);
return super.onSave(entity, id, state, propertyNames, types);
}
}
Same method can be used to monitor what values are getting saved. This helps the developer to debug the values that are passed to database.

Summary
This article is just an introduction to Hibernate Interceptors and includes few examples which reduces the redundant code and provides modularity from the core application logic.

No comments:

Post a Comment