16.1 Overview
Hibernate is all about Entity persistence and quite often we would want to intercept the request or perform some tasks when state of an object changes. With the help of interceptors , we get an opportunity to inspect the state of an object and if needed we can change the state as well.To support this Hibernate does support the concept of interceptor and provides a Interface and a implementation of Interface. Developers can either directly implement the Interface or may extend the implementation.
Similarly Hibernate comes with event and listener model which can be used to track the events .
16.2 Interceptors
16.2.1 Interceptor Interface API
Depending on requirement we can have a single interceptor instance for Session factory or we may create a new for a session
We can directly implement org.hibernate.Interceptor available in Hibernate. There are several methods available but most commonly used are –
- onSave()- this method is called before on object is getting saved and we can modify the state of an instance. Modified state will be persisted.
- onDelete()-is called before on object is getting deleted.
- onLoad()- is called before object initializes and when this method is called passed entity will be uninitialized.
- findDirty()- is called when flush method of session is called.
- preFlush()- is called before flush method is called (before database is updated).
- postFlush() – is called after flush method is completed (after database is updated)
To activate interceptor for a specific entities , you need to check the type of instance using instanceof operator else interceptor will work for all the entities.
16.2.2 EmptyInterceptor
Hibernate comes with an empty implementation of Interceptor interface under package org.hibernate with name EmptyInterceptor. It is always advisable to extend EmptyInterceptor instead of implementing Interceptor interface as we would just need to implement the required method and not the entire interface.
16.2.3 Configure Interceptor
Interceptor can be configured at a session level and at a session factory level.
- At a Session Level - Pass an instance of Interceptor while opening a session.
In hibernate 3 you can use SessionFactory.openSession( Interceptor) whereas in hibernate 4 SessionFactory.withOptions() .interceptor(Interceptor()).openSession();
This way we can get a control at a session level and can enable interceptor for a desired sessions only.
- At a Session Factory Level – Set an interceptor in a Configuration Object. Doing this , interceptor will be enabled for all the session for the session factory. Like Configuration.setInterceptor(Interceptor)
16.2.4 Example
Let's write an interceptor to log the audit statements on console.
a. Hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.url"> jdbc:mysql://localhost:3306/tutorial </property> <property name="hibernate.connection.username"> root </property> <property name="hibernate.connection.password"> password </property> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">true</property> <property name="hibernate.connection.driver_class"> com.mysql.jdbc.Driver </property> <mapping resource="book.hbm.xml" /> </session-factory> </hibernate-configuration>
b. Book.java
package com.tutorial.hibernate; public class Book { private int isbn; private String name; public int getIsbn() { return isbn; } public void setIsbn(int isbn) { this.isbn = isbn; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Book [isbn=" + isbn + ", name=" + name + "]"; } }
c. Book.hbm.xml
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.tutorial.hibernate.Book" table="Book"> <id name="isbn" type="int" column="book_id"> <generator class="native" /> </id> <property name="name" column="name" type="string" /> </class> </hibernate-mapping>
d. ConsoleLogInterceptor – ConsoleLogInterceptor is implementation of EmptyInterceptor and prints the object state. Also refer highlighted section to get an idea how to update the value. We are updating the name of the book
package com.tutorial.hibernate; import java.io.Serializable; import java.util.Iterator; import org.hibernate.EmptyInterceptor; import org.hibernate.Session; import org.hibernate.type.Type; public class ConsoleLogInterceptor extends EmptyInterceptor{ private static final long serialVersionUID = 1L; @Override public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { System.out.println("onDelete Method is getting called"); System.out.println("==== DETAILS OF ENTITY ARE ===="); if(entity instanceof Book) { System.out.println("Id of an Entity is :" + id); System.out.println("Property Names "); for(int i=0;i<propertyNames.length;i++) { System.out.println(propertyNames[i] ); } Book book = (Book) entity; System.out.println("BOOK STATE is "); System.out.println(book); } } @Override public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { System.out.println("onLoad Method is getting called"); System.out.println("==== DETAILS OF ENTITY ARE ===="); if(entity instanceof Book) { System.out.println("Id of an Entity is :" + id); System.out.println("Property Names "); for(int i=0;i<propertyNames.length;i++) { System.out.println(propertyNames[i] ); } Book book = (Book) entity; System.out.println("BOOK STATE is "); System.out.println(book); } return true; } @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { System.out.println("onsave Method is getting called"); System.out.println("==== DETAILS OF ENTITY ARE ===="); if(entity instanceof Book) { System.out.println("Id of an Entity is :" + id); System.out.println("Property Names "); for(int i=0;i<propertyNames.length;i++) { System.out.println(propertyNames[i] ); if("name".equals(propertyNames[i])) { state[i]= "Hibernate Tutorial Updated"; } } Book book = (Book)entity; System.out.println("BOOK STATE is "); System.out.println(book); } return true; } }
e. Create Book Table
CREATE TABLE 'book' ( 'book_id' int(11) NOT NULL AUTO_INCREMENT, 'name' varchar(255) DEFAULT NULL, PRIMARY KEY ('book_id') );
f. Test Program
import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import com.tutorial.hibernate.Book; import com.tutorial.hibernate.ConsoleLogInterceptor; public class Test { private static SessionFactory factory; public static void main(String args[]) { Configuration cfg = new Configuration().configure(); factory = cfg.buildSessionFactory(); SessionBuilder builder = factory.withOptions().interceptor(new ConsoleLogInterceptor()); Session session = builder.openSession(); Transaction tx = session.beginTransaction(); Book book = new Book(); book.setName("Hibernate tutorial"); int id = (Integer)session.save(book); session.delete(book); tx.commit(); session.close(); factory.close(); } }
g. Run Test Program – You can refer the logs on console. Also highlighted section confirms that name of the book has been updated.
16.3 Event System
Hibernate comes with an event system (a concept of listeners and event ) l and we can configure the events we want to track in the form of listeners. Most commonly used events are-
- Pre – Select: : these types of event are fired before executing any select statement.
- Post – Select: : these types of event are fired after executing any select statement.
- Pre – Insert: : these types of event are fired before execution of insert statement.
- Post – Insert: : these types of event are fired after execution of an insert statement.
- Pre – Update: : these types of event are fired before execution of update statements.
- Post – Update: : these types of event are fired after execution of any update statement.
- Pre – Delete: these types of event are fired before execution of any delete statement.
- Post – Delete: these types of event are fired after execution of any delete statement.
16.3.1 Implementing Listeners
Depending on the type of event we would need to implement the Listener interface available in org.hibernate.event package. Complete list of Event listeners can be seen at https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/event/package-summary.html
Event is getting passed in the listener and we can get the entity by calling getEntity() method of listener.
By default listener gets activated on all entities so we need to filter the entities using instaneof we are interested in.
package com.tutorial.hibernate; import org.hibernate.event.spi.PostUpdateEvent; import org.hibernate.event.spi.PostUpdateEventListener; import org.hibernate.event.spi.PreUpdateEvent; import org.hibernate.event.spi.PreUpdateEventListener; import org.hibernate.persister.entity.EntityPersister; public class UpdateBookEventListener implements PostUpdateEventListener, PreUpdateEventListener{ @Override public boolean onPreUpdate(PreUpdateEvent preUpdateEvent) { System.out.println("Pre Update Event "); if(preUpdateEvent.getEntity() instanceof Book) { Book book = (Book)preUpdateEvent.getEntity(); System.out.println(book.getName()); } return true; } @Override public void onPostUpdate(PostUpdateEvent postUpdateEvent) { System.out.println("Post Update Event "); if(postUpdateEvent.getEntity() instanceof Book) { Book book = (Book)postUpdateEvent.getEntity(); System.out.println(book.getName()); } } @Override public boolean requiresPostCommitHanding(EntityPersister arg0) { return false; } }
16.3.2 Enabling Event Listeners
Listeners can be enabled using hibernate.cfg.xml (till Hibernate3 ) .as below
Add the <event> tag in hibernate.cfg.xml file under <session-factory> tag like below .
<event type="pre-update"> <listener class="com.hibernate.tutorial.UpdateBookEventListener"/> </event> <event type="post-update"> <listener class="com.hibernate.tutorial.UpdateBookEventListener"/> </event>
With Hibernate 4 events configured in hibernate.cfg.xml files are ignored and custom integrator has to be used. Follow below steps
Create Custom Integrator by implementing Integrator interface
package com.tutorial.hibernate; import org.hibernate.cfg.Configuration; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventType; import org.hibernate.integrator.spi.Integrator; import org.hibernate.metamodel.source.MetadataImplementor; import org.hibernate.service.spi.SessionFactoryServiceRegistry; public class MyIntegrator implements Integrator { public void integrate( Configuration configuration, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); UpdateBookEventListener listener = new UpdateBookEventListener(); eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, listener ); eventListenerRegistry.appendListeners(EventType.POST_UPDATE, listener ); } @Override public void disintegrate(SessionFactoryImplementor arg0, SessionFactoryServiceRegistry arg1) { } @Override public void integrate(MetadataImplementor arg0, SessionFactoryImplementor arg1, SessionFactoryServiceRegistry arg2) { } }
b. Create a file with name org.hibernate.integrator.spi.Integrator inside META-INF/services directory . In this file you need to add the fully qualified class path of the integrator class like com.tutorial.hibernate.MyIntegrator
16.3.3 Example
Test Program
import org.hibernate.Session; Import org. hibernate. Session Builder; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.lock.UpdateLockingStrategy; import com.tutorial.hibernate.Book; import com.tutorial.hibernate.ConsoleLogInterceptor; public class Test { private static SessionFactory factory; public static void main(String args[]) { Configuration cfg = new Configuration().configure(); factory = cfg.buildSessionFactory(); Session session = factory.openSession(); Transaction tx = session.beginTransaction(); Book book = new Book(); book.setName("Hibernate tutorial"); int id = (Integer)session.save(book); tx.commit(); session.close(); session= factory.openSession(); tx= session.beginTransaction(); book = (Book)session.get(Book.class, id); book.setName("Updated"); tx.commit(); session.close(); factory.close(); } }
On running test program we can see the events fired