Interceptors

Interceptors work similarly to events, they enable you to inject call back operations when interacting with the session. Creating and using interceptors is simpler than events. Furthermore, you can enable interceptors on a specific session, whereas events are registered globally and will apply to all sessions. But you can also enable an interceptor on a session factory, so it applies to all sessions.

Hibernate defines an interface called org.hibernate.Interceptor that you would need to implement. But, it also provides an empty implementation that you can extend so that you won't need to implement every method of the interface.

Most of the call back methods on Interceptor return a Boolean data type to indicate whether the method has changed the state of the entity (the state of the entity is the disassembled version of the entity properties).

When working with interceptors, you may modify the state of the entity. In that case, you shouldn't modify the entity object itself if it's being passed as a parameter; however, in most methods, the state of each entity attribute can be passed as an array, and you can modify the right element of the array. Furthermore, the names of the class attributes are also passed as a second array. So, if you need to access an entity attribute by name, you will have to traverse the property name array.

Database trigger

This section shows how to write a database trigger using interceptors. To demonstrate this, we assume that your business contains some sensitive data, and the administrators need to track users who are looking at this data. Obviously, you would want to implement role-based access in your application to ensure that only authorized users can access the data. But, in some cases, you may want to further monitor which users are looking at which data.

We begin by create and audit entity:

@Entity
public class AuditTrail {

  @Id
  @GeneratedValue
  private long id;
  private String username;
  private Long entityId;
  @CreationTimestamp
  private Date createTimestamp;

   // getters and setters
}

Next, we create the interceptor by extending the EmptyInterceptor class. Note that the Interceptor interface provides several methods to respond to various events, such as save, delete, and flush, and transaction events. Here, we only show an example of onLoad, but you can implement any of the methods defined by the Interceptor interface:

public class MyInterceptor extends EmptyInterceptor {

  @Override
  public boolean onLoad(Object entity,
      Serializable id,
      Object[] state,
      String[] propertyName,
      Type[] types) {

    if (entity instanceof Person) {
      AuditTrail audit = new AuditTrail();
      audit.setUsername(ServiceContext.getUsername());
      audit.setEntityId((Long) id);
      
      boolean transactionStarted = false;

      Session session = HibernateUtil.getSessionFactory()
        .getCurrentSession();
      Transaction transaction = session.getTransaction();
      try {
        if (!transaction.isActive()) {
          transaction.begin();
          transactionStarted = true;
          session.save(audit);
          transaction.commit();
        }
        else {
         session.save(audit);
        }
      }
      catch (Exception e) {
        if (transactionStarted) {
          transaction.rollback();
        }
      }
    }
    
    return false;
  }
}

There are a few things to note about the interface interceptor:

  • It obtains the current session instead of opening a new session. You can certainly do this work in a brand new session, which is only recommended if you are using JTA or some other two-phase commit mechanism. That way, both sessions (the current and the session the interceptor is acting on) are within the same transactional scope, so if one fails, the other can roll back as well. Also note that this interceptor doesn't close the session. But, if you are opening a new session, you should close it.
  • It's always a good idea to check whether a transaction has already started before you start a new one, in cases where you are managing transactions. Keep in mind that, if the underlying transaction mechanism is JDBC, Hibernate can only guess the status of the transaction.
  • This interceptor only acts when the entity is of the Person type. It doesn't care about the other entity types being loaded in this session. This is done by checking the class type of the entity, using the if block.
  • The username of the person who is fetching the sensitive entity comes from a ThreadLocal variable. We assume that your application sets this variable. If there are other ways in your application to identify the user who owns the request, use those.

Now, let's look at sample code that uses this interceptor. Note that we assume that, somewhere in the execution path, the username is set using the ThreadLocal variable we talked about earlier (in this case, this is done on the class called ServiceContext):

ServiceContext.setUsername("James");
…
session = HibernateUtil.getSessionFactory()
    .withOptions()
    .interceptor(new MyInterceptor())
    .openSession();
transaction = session.beginTransaction();
try {
  Query query = session.createQuery("from Person");
  
  List<Person> persons = query.list();
  
  for (Person person: persons) {
    System.out.println("person loaded: " + person);
  }
  transaction.commit();
} catch (Exception e) {
  transaction.rollback();
  throw e;
} finally {
  if (session.isOpen())
    session.close();
}

It is important to note how we are obtaining the session. In order to apply your interceptor to the session, you would need to use the SessionBuilder interface whose implementation is returned by calling the withOptions() method on the session factory. If your session is auto-wired, for example, by Spring, you can obtain the session factory from the session by calling session.getSessionFactory() and start a new session with the Interceptor option. Keep in mind that this will be a different session from the one Spring auto-wires.

If you write an interceptor like the previous one, you'll have to remember that this will fill up your database very quickly. You'll have to either do some cleanup occasionally or consider indexing and partitioning your audit table.

There are other use cases for Interceptors. A common one involves storing the user's password in the database. You can use an interceptor that encrypts the password before storing it in the database instead of storing the passwords as clear text. Obviously, you would need another interceptor to decrypt it.

Event or interceptor

Now that we have demonstrated both the event and interceptor mechanisms, you may wonder which is better. In short, the Event architecture is more sophisticated, but it is cleaner. Furthermore, you have access to the ongoing session and you don't have to worry about session creation or transaction demarcation. Every event type provides access to the session object with which you can interact.

The DB trigger example that we demonstrated earlier uses the interceptor mechanism. It is responsible for creating its own session and demarcating transactions. You will not have to do this with events.

On the other hand, interceptors are simpler to create and configure unlike events, which have to be registered as a service.

It's best to use interceptors for simple and straightforward tasks, and preferably not for interacting with the persistence context; instead, use events for tasks that require complicated interaction with the persistence context.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.174.253