Search This Blog

Tuesday, 10 July 2012

Listeners in Hibernate

Hibernate 3.x is built on the concept of events and listeners.To deal with the different operations like loading an object, saving an object, Hibernate creates events. There exists listeners in the Hibernate framework listening for these events.
This is the code that executes when we make a load() call.
public Object load(String entityName, Serializable id) throws HibernateException {
    LoadEvent event = new LoadEvent(id, entityName, false, this);
    boolean success = false;
    try {
        fireLoad( event, LoadEventListener.LOAD );
        if ( event.getResult() == null ) {
            getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityName, id );
        }
        success = true;
        return event.getResult();
    }
    finally {
        afterOperation(success);
    }
}
 As can be seen,
  1. Hibernate creates a LoadEvent and delegates it to the appropriate Listener
  2. The LoadEvent includes all the information necessary for the call. This would mean the entity class, the identifier to use , the LockMode to use, do the associations need to be fetched etc. 
  3. The Listener works on the LoadEvent adding the loaded object to the event.
The advantage of this design is that the entire system is built up of separate chunks. If we need to change a particular behaviour we can do it by building our own custom listener to handle a specific event.
Consider the custom listener I implemented which does nothing special other then write a few details to the logs.
public class LoadEventListener extends DefaultLoadEventListener {

    private static final Logger logger = Logger
            .getLogger(LoadEventListener.class);

    @Override
    public void onLoad(LoadEvent event, LoadType loadType)
            throws HibernateException {
        logger.info("onLoad:  loading object for event " + event
                + " and loadType " + loadType);
        logger.info("onLoad: getEntityClassName : "
                + event.getEntityClassName());
        logger.info("onLoad: getEntityId : " + event.getEntityId());
        logger.info("onLoad: getInstanceToLoad : " + event.getInstanceToLoad());
        logger.info("onLoad: getLockMode - " + event.getLockMode());
        logger.info("onLoad: getSession - " + event.getSession());
        logger.info("onLoad: getResult - " + event.getResult());
        super.onLoad(event, loadType);
    }
}
The next step is to associate the listener with Hibernate framework. This is done as a part of the Hibernate configuration.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>

        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">root</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/objmod</property>
        <property name="hibernate.format_sql">true</property>
        <property name="show_sql">true</property>

        <mapping resource="com/listener/Entity.hbm.xml" />

        <event type="load">
            <listener class="com.listener.LoadEventListener" />
        </event>        
    </session-factory>
</hibernate-configuration>
The listener is configured in the cfg file as seen in the highlighted code. It can also be added programatically.
Configuration configuration = new Configuration();        
    org.hibernate.event.LoadEventListener[] listeners = { new LoadEventListener()};     
    configuration.getEventListeners().setLoadEventListeners(listeners);
    configuration = configuration.configure();
    sessionFactory = configuration.buildSessionFactory();
If you look at the code we are not adding listeners. We are setting all the listeners. To ensure that Hibernate works correctly for the load mechanism, it is very important that we add the DefaultLoadEventListener as the first entry in the array. In our case as we are extending the DefaultLoadEventListener everything is fine (also the super() call in the end ensures smooth working). (To verify the above simply replace the base class with  org.hibernate.event.LoadEventListener interface)
To test our code we try to load an entity
public static void testLoad() {
    final Session session = sessionFactory.openSession();
    Entity entity = (Entity) session.load(Entity.class, 1L);
    System.out.println("value is " + entity);
}
The logs indicate the below:
3109 [main] INFO  com.listener.LoadEventListener  - onLoad:  loading object for 
event org.hibernate.event.LoadEvent@497934 and loadType LOAD
3109 [main] INFO  com.listener.LoadEventListener  - onLoad: getEntityClassName :
 com.listener.Entity
3109 [main] INFO  com.listener.LoadEventListener  - onLoad: getEntityId : 1
3109 [main] INFO  com.listener.LoadEventListener  - onLoad: getInstanceToLoad : 
null
3109 [main] INFO  com.listener.LoadEventListener  - onLoad: getLockMode - NONE
3109 [main] INFO  com.listener.LoadEventListener  - onLoad: getSession - Session
Impl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=
[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collecti
onUpdates=[]])
3125 [main] INFO  com.listener.LoadEventListener  - onLoad: getResult - null
3125 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - loading en
tity: [com.listener.Entity#1]
3125 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - creating n
ew proxy for entity
3156 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - object not
 resolved in any cache: [com.listener.Entity#1]
3156 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Fetc
hing entity: [com.listener.Entity#1]
...
    select
        entity0_.ID as ID0_0_,
        entity0_.DATA as DATA0_0_ 
    from
        LISTENER_ENTITY entity0_ 
    where
        entity0_.ID=?
...
3218 [main] DEBUG org.hibernate.loader.Loader  - done entity load
value is com.listener.Entity@128f6ee
As can be seen, first our event listener was called which then delegated to the default LoadEvent listener.

7 comments:

  1. Hi
    I want to stop child entity set from getting loaded, so that i can add the loading implementation of my own.
    What i notice with hibernate is, it first loads then calls setter method. I want to control this behavior of hibernate(stop loading an association) and provide my own implementation to fetch the association set.

    ReplyDelete
  2. Hi,
    You should try to write a custom loader for the same. Check this link - http://learningviacode.blogspot.in/2012/01/creating-custom-entity-loaders.html

    ReplyDelete
  3. Hi Robin,
    I had checked this post http://learningviacode.blogspot.in/2012/01/creating-custom-entity-loaders.html.
    But i am worried this may bring in great changes to the existing framework which is not expected. So i would like to write a listener PreLoad kind of, so that i could stop the entity manager from loading and give my loading implemention. As a listener would be a most generic change, i have to opt this. Please give me some examples of this sort. The problem is whenever the session is about to load a collection that is OneToMany mapped with a Parent, we need to stop it from getting loaded, and give our implementation. I have prepared this, but it lead to a very big recursive invoke of the same method and i am totally blocked here :(

    ReplyDelete
  4. Tried the Hibernate Event API in a JTASessionContext on a Glassfish/Derby server with JNDI-Datasources.
    No Events were catched.

    ReplyDelete
    Replies
    1. My bad! Realized, that your description is for Hibernate 3.x, while I'm using 4.x and 4.x needs Integrators.

      Delete
    2. What is the replacement for Listeners in Hibernate 5.x

      Delete
  5. Will Listeners work even if entries are being added through other application in same Database?

    ReplyDelete