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.
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:
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.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.
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.
3.145.174.253