3

hibernate dirty flag works great for what they call "persistent" objects https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/objectstate.html

but i have a web app, where the hib mang entity Order is also used as the Spring MVC command object. so when my controller marshals the request into an order, it is what hibernate calls a "detached" object. my problem senario can be illustrated with 2 web clients and 4 web requests against a single order.

1 - client 1 opens the order in detail view
2 - client 2 opens the same order and updates attribute 1
3 - client 1 clicks "save" to update any changed fields, but actually has not updated anything

in step 3, client 1 issues session.update(order) (also tried session.merge() and session.saveOrUpdate()) and ends up reverting attribute 1 back to its original value. if it was a "persistent" object, then hibernate would have known that nothing had changed. but because it is a detached object, hibernate actually issues an update for every member of the Order object.

Any ideas on how to get the dirty flag in hibernate detached objects to work like it does in persistent objects?

test first test shows hibernate working with persistent objects, works great, session 1 issues no updates:

try{Class.forName("net.sourceforge.jtds.jdbc.Driver");} catch(Exception e){e.printStackTrace();}

Session session1 = null;
Session session2 = null;


SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
session1 = sessionFactory.openSession();
session2 = sessionFactory.openSession();

Transaction tx1 = session1.beginTransaction();
Transaction tx2 = session2.beginTransaction();


// order 1 has a null dig user
//session 1 opens order 1
//session 2 opens order 1
//session 2 assigns andrew as dig, save
//session 1 hits save


Query qo1 = session1.createQuery("select o from  Order o where o.id = 536258");
Order o1 = (Order)qo1.list().get(0);

Query qo2 = session2.createQuery("select o from  Order o where o.id = 536258");
Order o2 = (Order)qo2.list().get(0);

 Query qu2 = session2.createQuery("select u from  User u where u.id = 7");
 User u2 = (User)qu2.list().get(0);

System.out.println("update user session 2 begin");
 o2.setDigitizer(u2);
System.out.println("update user session 2 end");


System.out.println("session 2 save begin");
session2.save(o2);
session2.flush();
tx2.commit();
session2.close();
System.out.println("session 2 save end");

System.out.println("session 1 save begin");
session1.save(o1);
session1.flush();
tx1.commit();
session1.close();
System.out.println("session 1 save end");

then here is the same idea but with detached objects and client 1 ends up reverting the update made by client 2:

try{Class.forName("net.sourceforge.jtds.jdbc.Driver");} catch(Exception e){e.printStackTrace();}

Session session1 = null;
Session session2 = null;
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();

System.out.println("session 1a start");
session1 = sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();
Query qo1 = session1.createQuery("select o from  Order o where o.id = 536258");
Order o1 = (Order)qo1.list().get(0);
session1.flush();
tx1.commit();
session1.close();
System.out.println("session 1a end");


System.out.println("session 2 start");
session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
Query qo2 = session2.createQuery("select o from  Order o where o.id = 536258");
Order o2 = (Order)qo2.list().get(0);
Query qu2 = session2.createQuery("select u from  User u where u.id = 7");
User u2 = (User)qu2.list().get(0);
o2.setDigitizer(u2); //here is session 2 update to the order
session2.flush();
tx2.commit();
session2.close();
System.out.println("session 2 end");

//here is the "detached" part, new session, but same domain object instance from above
System.out.println("session 1b start");
session1 = sessionFactory.openSession();
tx1 = session1.beginTransaction();
session1.update(o1); //tried merge -- same result, we revert the session 2 update
session1.flush();
tx1.commit();
session1.close();
System.out.println("session 1b end");
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
tom
  • 2,190
  • 1
  • 23
  • 27

2 Answers2

2

You don't need to call save if the entity is detached. That is meant only for persisting a transient entity.

In your case you need to merge the detached entity and make sure you use optimistic locking too, because you use two different Sessions (one for loading and the second for updating) and meanwhile the actual database object might have changed by some other concurrent request.

If you use property-level access, then you need to make sure you don't mess with the actual Hibernate collections, like when wrapping them into some other collection object. In that case, Hibernate will detect those collections as dirty, even if the actual content hasn't changed.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • may you look at this question http://stackoverflow.com/questions/27602318/hibernate-myinterceptoronflushdirty-is-never-called pls? – VB_ Dec 22 '14 at 16:44
  • All objects retrieved through Session querying are attached. The only time you need to call save is for new entities. – Vlad Mihalcea Dec 26 '14 at 19:27
  • Thanks for following up Vlad. I looked into detatched objects -- yes, you are right. So in the facade the a previous team set up, my "save", I see now was actually calling getHibernateTemplate().merge(o); So I have tried merge() and update() to update my detatched objects and still I am seeing the 1st session with nothing changed, overwriting (reverting) the value set by the 2nd session. I will make a new test that uses detached objects and see if I can reproduce it. } – tom Dec 26 '14 at 22:43
  • One thing I am learning about "merge()" and the dirty flag for detached objects, is that first hibernate loads the full object (and all members) then it sees that the one thing that does not match is the digitizer which client 2 has updated (see test 2, updated in my post), then it issues that single dynamic-update statement: Hibernate: /* update com.richardsoncap.ews.model.Order */ update Orders set digitizer_id=? where Id=? So how would hibernate (or I for that mater) know that order.digitizer has not changed for the current client so should not get the dirty flag/update? – tom Dec 26 '14 at 23:53
1

you're pretty much stuck using an interceptor for this. hibernate is VERY AGGRESSIVE about detecting deltas on an object, to the point of not caring if the value actually deltas, only that the mutator methods are called.

it is annoying but the overhead of recursive descent and the possibility of circularity preclude a more definitive dirty detection on an abstract level.

him
  • 608
  • 5
  • 15