-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 
Author Message
 Post subject: Access to Hibernate's "dirty checking" feature?
PostPosted: Fri Oct 31, 2008 3:34 am 
Beginner
Beginner

Joined: Thu Oct 16, 2008 11:19 am
Posts: 23
Location: Norway
As I understand it Hibernate has a "dirty checking" feature which it uses to only update the fields that have been modified. Is it possible to get access to this feature to check if an object has been modified?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Oct 31, 2008 6:44 am 
Newbie

Joined: Fri Oct 31, 2008 6:27 am
Posts: 4
In the org.hibernate.Session you have the isDirty() method that checks
if your session contains dirty objects.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Oct 31, 2008 7:07 am 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
There is the Session.isDirty() method, but it doesn't take any parameter and I suppose it checks every object in the session. It may be possible to do something with a custom interceptor though. The idea I have is that the session is create with an interceptor that implements the findDirty() method.

The normal operation is to return null to make Hibernate use it's default dirty checking methods. Now, if you want to check if a specific object is dirty you first give this object to the interceptor. The interceptor's findDirty() method should now return null only for the object it was given and an empty array for all other objects. Eg. something like this:

Interceptor:
Code:
public void setDirtyCheckObject(Object entity)
{
   this.givenObject = entity;
}

public int[] findDirty(Object entity, .....)
{
  if (givenObject == null || entity ==givenObject)  return null;
  return EMPTY_ARRAY;
}


Dirty check code:

Code:
public boolean isDirty(Session session, Object entity)
{
  try
  {
    interceptor.setDirtyCheckObject(entity);
    return session.isDirty();
  }
  finally
  {
     interceptor.setDirtyCheckObject(null);
  }
}


I have no idea if this is working or not, or if it may have any side-effects on other things. It may be worth a try though. It may be kind of slow if you need to dirty-check several objects when there are many objects in the session.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Nov 05, 2008 9:03 am 
Beginner
Beginner

Joined: Thu Oct 16, 2008 11:19 am
Posts: 23
Location: Norway
Thanks for the replies, both! :)

If I make an interceptor, which I don't have any experience with, what do I do with all the other methods in the Interceptor interface? There are quite a few of them.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Nov 05, 2008 9:05 am 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Your interceptor can extend the EmptyInterceptor class and only override the methods that you need to change.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Nov 05, 2008 10:16 am 
Beginner
Beginner

Joined: Thu Oct 16, 2008 11:19 am
Posts: 23
Location: Norway
Thanks, that got me a little further. Now I have the problem that the first time I run it I get a NullPointerException in the line:

interceptor.setDirtyCheckObject(null);

...and then if I comment out that line I get a NullPointerException in (also the first time):

interceptor.setDirtyCheckObject(entity);

But, the second time I try it it works. Unfortunately it also saves the changes at the same time, which is not what I had in mind. That is also the case if I try to use session.isDirty() alone.

Any ideas?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Nov 06, 2008 5:46 am 
Beginner
Beginner

Joined: Thu Oct 16, 2008 11:19 am
Posts: 23
Location: Norway
Although I'm not 100% sure how, I managed to get rid of the exceptions by adding these lines:

Code:
public boolean isDirty( Session session, Object entity )
{
    try
    {
        interceptor.setDirtyCheckObject( entity );
        session.lock( entity, LockMode.READ );
        return session.isDirty();
    }
    finally
    {
        session.lock( entity, LockMode.NONE );
        interceptor.setDirtyCheckObject( null );
    }
}


Is there any way to make it not save the changes at the same time?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Nov 06, 2008 6:09 am 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
The session can of course only check the dirtiness of objects that are managed by the session. If you have an entity that is not managed by the session the call to session.isDirty() will of course never propagate to the interceptor for that object. The addition of session.lock() makes the whole procedure useless for unmanaged entities since this sets the "zero"-point for the entity. The isDirty() method will only find changes made to the object after the session.lock() was called.

If you don't want to save changes you need to evict() the object before the session is flushed.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Nov 06, 2008 5:19 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
I was trying to understand a bit more about how the dirty checking mechanism in Hibernate works and I think I discovered some things that makes the approach with an interceptor not to work.

The "problem" is that if the Session.isDirty() method finds that the session is dirty this information is cached internally and subsequent calls will simply return true until the session is flushed or cleared. So the approach with the interceptor will only work as long as the objects passed to it are not dirty.

It also seems like the dirty checking is piggy-backed to a flushing event that flushes changes to an internal queue holding actual SQL operations. However, the actual operations are not executed against the database until a real flush occurs. I am a bit worried that this may lead to that a call to evict() may not actually remove the queued SQL actions. It would be interesting to know what happens when something like this is executed:

Code:
Session session = sf.openSession();
Transaction tx = session.beginTransaction();
SomeObject entity = session.get(....);
entity.setAProperty("aNewValue");
session.isDirty();
session.evict(entity);
tx.commit();


Will the call to session.isDirty() add an action to the internal queue that updates the AProperty to aNewValue even though the entity is evicted before the transaction is committed?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Nov 06, 2008 6:31 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Ok, I have some more information and a new approach. I turned out that the code in the previous post was not problematic. The dirty check does clear some of the internal queues (including the update queue) before returning. Thus, the commit() never sends an update for the evicted entity.

I have been trying to implement something based on the code in the SessionImpl class. It is a rather ugly cut-and-paste of what I think is the relevant parts of the Session.isDirty() method. I may be missing a lot but so far it seems to be working.

Code:
public boolean isDirty(Session s, Object entity)
{
  // We need a lot of functionality in SessionImpl
  SessionImpl session = (SessionImpl)s;
  boolean dirty = false;

  // From DefaultDirtyCheckEventListener.onDirtyCheck()
  int oldSize = session.getActionQueue().numberOfCollectionRemovals();
  try
  {

    // From AbstractFlushingEventListener.flushEntities()
    FlushEntityEventListener[] flushEntityEventListener =
      session.getListeners().getFlushEntityEventListeners();
    EntityEntry entry = session.getPersistenceContext().getEntry(entity);
    FlushEntityEvent entityEvent = new FlushEntityEvent(session, entity, entry );
    for (int i = 0; i < flushEntityEventListener.length; i++)
    {
      flushEntityEventListener[i].onFlushEntity(entityEvent);
    }

    // From DefaultDirtyCheckEventListener.onDirtyCheck()
    dirty = session.getActionQueue().hasAnyQueuedActions();
  }
  finally
  {
    // From DefaultDirtyCheckEventListener.onDirtyCheck()
    session.getActionQueue().clearFromFlushNeededCheck(oldSize);
  }

  return dirty;
}


The code seems to work with at least simple cases. It needs to be polished up a lot. Among other things:

* Define Session.isDirty(Object) and implement in SessionImpl
* Maybe introduce DirtyCheckEntityEvent and DirtyCheckEntityEventListener interfaces to make the design similar to a flush that uses FlushEvent/FlushEventListener and FlushEntityEvent/FlushEntityEventListener.
* Check what happens when only collections are dirty. There seems to be other tracks in the flush event handling that handles this.


Top
 Profile  
 
 Post subject: Re: Access to Hibernate's "dirty checking" feature?
PostPosted: Sat Aug 28, 2010 1:14 pm 
Newbie

Joined: Sat Aug 28, 2010 7:30 am
Posts: 2
Here is another approach to get this working - http://www.brimllc.com/2010/08/hibernate-automatic-dirty-check-for-detached-objects/


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.