Hibernate Automatic Dirty Check for Detached Objects

Home / Blog / Java / Hibernate / Hibernate Automatic Dirty Check for Detached Objects

Per the Hibernate documentation, Hibernate can perform dirty checks only when the objects are loaded and changed in the scope of a single Session. This means we cannot use detached objects, but we must keep our session open for one conversation (multiple http requests) using Managed Sessions, just to get hibernate’s dirty check. This introduces new challenges of storing the Hibernate Session object in HttpSession, which fails if we deploy in a clustered environment, since most of the fields in Hibernate org.hibernate.impl.SessionImpl class are transient. So, how do we get the benefit of using Detached Objects and also get Hibernate’s automatic dirty checking?

The following is a brief background of how Hibernate does its dirty checking:

Hibernate Session contains a PersistenceContext object, which maintains a cache of all the objects read from the database as a Map. In the same Session, when I read an object from the db and make changes to it, Hibernate compares the objects and triggers the updates when the session is flushed. Every object in the PersistenceContext is called Persistent object. Once the session closes, the PersistenceContext is lost and so is the cached copy. A detached object, when saved, opens a new session containing an empty PersistenceContext, so there is nothing to compare against for the dirty check.

If we have an original cached copy of the Detached Object (a clone saved in HttpSession) and we are able to place that copy in the PersistenceContext somehow, we can get the dirty check to work. Here is how to do it.

Couple of points here, before we start reading the code -

1)  All your domain models have a generic way to expose their primary key. Here I have defined PrimaryKeyAwareDomainModelObject interface, which exposes a method to access the primary key property.

public interface PrimaryKeyAwareDomainModelObject {
public Integer getPrimaryKey();
}

2) You already have the original cached copy of the Detached Object, and have made changes to service layers to pass it to the DAO layer.

3) Also, your Detached Objects are POJO’s.

4) You are using Spring HibnerateDaoSupport, else you can get the SessionFactory to access the Hibernate Session.



/*** This class handles adding the cached Detached Object to PersistenceContext of a new Session.

** 1) Get a new session, or the session associated with this transaction from the
* sessionFactory.

*2) Inside the PersistentContext of the session, add the old
* Model object, so hibernate thinks that it was loaded as part of read
* operation in the same session

*3) Copy the changed values from the new Model
* object to the old Model object, since hibernate does identity check on the
* object in the persistenceContext, passing a new Model directly to hibernate
* will not trigger dirty checking, but create issues.

*4) Call usual saveOrUpdate, which will check the object if its dirty before update.
***/

import org.hibernate.EntityMode;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.PersistenceContext;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.Status;
import org.hibernate.impl.SessionImpl;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class HibBeforeUpdateListener<T extends PrimaryKeyAwareDomainModelObject, Q extends HibernateDaoSupport> {

/***
* Before doing a call on saveOrUpdate on hibernate session - fetch the
* session - get the PersistenceContext, and add the oldModelObj - copy the
* modified fields from new modelObj to oldModelObj.
* You can pass SessionFactory directly instead of HibernateDaoSupport
*/
@SuppressWarnings("unchecked")

public T beforeUpdate(T modelObj, T oldModelObj, Q dataAccessor) {

SessionFactory factory = dataAccessor.getSessionFactory();
Session session = factory.getCurrentSession();
// get the PersistenceContext of this session
PersistenceContext persistenceContext = session instanceof SessionImpl ? ((SessionImpl) session).getPersistenceContext(): null;
if (null != persistenceContext) {
addEntityToPersistenceContext(persistenceContext, oldModelObj,(SessionFactoryImplementor) factory);
}
// copy modified values from newModel to oldModel

// I am using Dozer

return (T) DozerUtility.copy(modelObj, oldModelObj);
}

private EntityKey generateKey(T modelObj, SessionFactoryImplementor sessionFactory) {
// EntityKey is the key used in the map of objects stored in
// PersistenceContext  it requires the primary key, defined by the @Id annotation in the model class
// , persister object which is SingleTableEntityPersister class in my case, and our models type, which is POJO
return new EntityKey(modelObj.getPrimaryKey(), sessionFactory.getEntityPersister(modelObj.getClass().getCanonicalName()),EntityMode.POJO);
}

/***
 * Hibernate stores the entities as an EntityEntry inside a IdentityMap, ie.
 * 2 objects are equal, if they reference the same object, rather than
 * Object.equals and hashcode is equal. EntityEntry contains the original
 * object as loaded from the db, plus also its original state as an Object
 * array, called loadedState. This method mimics the same behavior of
 * hibernate internals, and stores the object and its loadedState.
 *
 * @param context
 * @param oldModelObj
 * @param sessionFactory
 */
 private void addEntityToPersistenceContext(PersistenceContext context, T oldModelObj, SessionFactoryImplementor sessionFactory) {
       Map<String, T> allEntitiesMap = new HashMap<String, T>();
       for(T modelObj : getAllEntityObjects(oldModelObj, allEntitiesMap).values()){
                 context.addEntity(modelObj, Status.MANAGED, sessionFactory.getEntityPersister(modelObj.getClass().getCanonicalName())
                                 .getPropertyValues(modelObj, EntityMode.POJO), generateKey(
                                  modelObj, sessionFactory), null, LockMode.READ, true,
                                  sessionFactory.getEntityPersister(modelObj.getClass().getCanonicalName()), false, true);
     }
 }

 /***
 * Make a list of all sub Entity objects instances inside the object,
 * and add them separately
 * @return
 */
 private Map<String, T> getAllEntityObjects(T object, Map<String, T> allEntityObjects){
   // definitely this object will be added to persistence context
   if(!allEntityObjects.containsKey(object.getClass().getCanonicalName())){
            allEntityObjects.put(object.getClass().getCanonicalName(), (T)object);
    }
   // get all the instance variables and check their annotations
   Field[] fields = object.getClass().getDeclaredFields();
   // if no fields (impossible scenario), then just add the object and send it back
    if(fields.length == 0){
     return allEntityObjects;
   }
   for(Field f : fields){
    Class fieldClass = f.getType();
    if(fieldClass.getAnnotation(javax.persistence.Entity.class) != null){
     try{
         f.setAccessible(true);
         Object fieldValue = f.get(object);
        // only add the domain model objects
         if(fieldValue instanceof PrimaryKeyAwareDomainModelObject && !allEntityObjects.containsKey(fieldClass.getCanonicalName())){
            allEntityObjects.put(fieldClass.getCanonicalName(), (T)fieldValue);
            getAllEntityObjects((T)fieldValue, allEntityObjects);
         }
      }catch(IllegalAccessException ex){
        // do nothing and move to next field
      }
    }
  }
    return allEntityObjects;
 }
}

And here is how to use it in a DAO -


public class MyDataAccessorImpl extends
HibernateDaoSupport{

public void update(T modelObj, T oldModelObj) {
PrimaryKeyAwareDomainModelObject modelObjToUpdate = new HibBeforeUpdateListener().beforeUpdate(modelObj, oldModelObj, this);
getHibernateTemplate().saveOrUpdate(modelObjToUpdate);
}

}

This approach was the most efficient that used Hibernate’s own public APIs. If anyone else has a similar approach to the same problem, I would love to hear…

Showing 5 comments
  • tmillhouse

    This is a great overview of implementing dirty checking across session boundaries in hibernate. I know many people are aware of filters such as Spring’s OpenSessionInViewFilter, but that is only good across a single request. This approach outlined above goes above and beyond, and it really has many uses!

  • Makarska

    Thanks for this great post. The info I have gained from your blog is truly encouraging

  • Amit

    This article really helped me a lot. The concept of “Hibernate Session contains a PersistenceContext object, which maintains a cache of all the objects read from the database as a Map” is very helpful to me.

    I was using Interceptor class to check the dirty state of the session but the onFlushDirtymethod was never been called while I’m executing the query.executeUpdate() just after opening a new session.

    but now I’m able to see the changes first loading the entity in session and then changing some of the property value of it and right after call to session.isDirty() triggers the call to onFlushDirty() in the interceptor.

    This interceptor I’v passed to session using overloaded sessionFactory.openSession(new MyInterceptor())

  • Rahul

    I am guessing this but you would have configured this listener in the session factory right? Thats how your custom code got plugged in to hibernate..

  • Yagish Sharma

    I used a wrapper DAO class which uses this listener to check the object is dirty before doing save or update. This does not hooks into hibernate code, but uses hibernate’s PersistentContext to fetch the object read from the database and then compare with the session cached object. You can do something similar to hook this up in your code.

Leave a Comment