Why doesn’t hibernate automatically update changed objects?

February 7, 2009

Have you ever asked yourself this question? Have you ever been surprised that the changes made to a persistent object are not committed to the database? Well this is a common problem and can be caused by many things. One nasty cause is if you are using a common hibernate batch processing pattern where session.flush and session.clear are used to manage memory and improve performance. Do you see the problem with the following code snippet?

public void doBatch() {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
List personList = session.createQuery("from Person").list();
int i = 0;
for (Person person : personList) {
    person.setName("newName"); // this change should be caught by hibernate and cause the an update statement to be generated
    if ( ++i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}
tx.commit();
session.close();
}

You might expect that every Person object that didn’t already have “newName” for the name value would be updated in the database with that new value. That is the correct assumption for the first 20 Person objects. However #21 on will not be updated in the database. This is because when hibernate has persistent objects in the first level cache it compares any changes to the field values of the object to the values in the cache. When a field changes hibernate schedules an update for the object in the database. Calling session.clear() clears the first level cache which is great for saving memory but doesn’t help with auto detecting changes to persistent objects. There are two easy solutions to this problem:

public void doBatch() {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
List personList = session.createQuery("from Person").list();
int i = 0;
for (Person person : personList) {
    String newValue = "newName";
    if (!newValue.equals(person.getName()) {
        session.update(person);  // need to manually call update because object may not still be in first level cache
    }
    person.setName(newValue); 
    if ( ++i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}
tx.commit();
session.close();
}

Notice the manual check for a value change and the call to session.update. The manual check prevents updating objects where no values have changed.

Another solution is to use a scrollable resultset. When a scrollable result set is used the persistent object is not loaded into the cache until resultset.next() is called. This way you do not have to worry about the object being cleared from the cache before you make changes to it.

Advertisements