Why doesn’t hibernate automatically update changed objects?

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.

About these ads

6 Responses to Why doesn’t hibernate automatically update changed objects?

  1. Eric Kaufman says:

    We use NHibernate for one of our bigger projects, and a frequent frustration is “WHY IS HIBERNATE DOING THAT??!?!?!”. All in all it’s still an amazing piece of software that has saved a lot of time and energy. Regular property stuff isn’t too big of an issue for us; the mind bending problems are dealing with bags. Trying to figure out what’s happening on that end, when you have to take into account all the cascade options and collection types, is not for the weak of heart.

  2. ZagIT says:

    Hence the correct way to do this batch update would look like this:

    public void doBatch() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();

    ScrollableResults personList = session.createQuery(“from Person”).setCacheMode(CacheMode.IGNORE).scroll(ScrollMode.FORWARD_ONLY);

    int count=0;
    while (personList.next() ) {
    Person person = (Person) personList.get(0);
    person.setName(“newName”);
    if ( ++count % 20 == 0 ) {
    //flush a batch of updates and release memory:
    session.flush();
    session.clear();
    }
    }

    tx.commit();
    session.close();
    }

  3. John Summers says:

    Very well spotted. In fact the Hibernate documentation example does show ScrollableResultSet for the batch UPDATE example but typical of the Hibernate communitys *appalling* attitude to documentation they fail to mention WHY this is important in avoiding the bug you point out here.

  4. Lav says:

    i didnt understand this , suppose the loop reaches 21st object then it compares what to what ? you say ” … it compares any changes to the field values of the object to the values in the cache.” now so as per my understanding it will try to compare 21st object to an object in cache BUT the cache is empty HENCE ( as after 20th object we cleared it ) so comparison should result false and hence update should happen … i am not sure why comparison will result in true

  5. Lav says:

    my first ques remains as is, adding a second one >> If we go via the 1st approach THEN the purpose of batch update is deleted , as each object after 20th will be explicitly update , right ??

  6. abramsm says:

    Lav – In you first question the problem is that the first level cache is empty. So it doesn’t know if the object is new or updated. For your second question the answer is no. The update call simply queues the update to be flushed to the DB the next time flush() is called.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: