Search This Blog

Saturday 25 February 2012

Making A Detached Object Persistent Again

Hibernate allows detached objects to be associated with a persistence context again.One way to achieve this is to use the update method of the session.
public static void reattachUsingUpdate() {
    Session session1 = sessionFactory.openSession();
    Entity entity = (Entity) session1.get(Entity.class, 1);
    //The object is now persistent
    session1.close();
    //The object is now detached
    //no dirty marking on it currently
    entity.setData("Modifying the value outside session");
    Session session2 = sessionFactory.openSession();
    Transaction transaction = session2.beginTransaction();
    session2.update(entity);
    //The object is now persistent again
    transaction.commit();
    session2.close();
}
The update method forces an update operation to be fired for the passed object. As this object is detached, Hibernate adds this object to the persistence context (making it persistent) and treats it as dirty. Hibernate now schedules an SQL Update query which is fired when the session is flushed - either on transaction commit/before execution of a (related) query or on explicit call to session.flush().
In this case, even if no changes are made to the object the SQL query will be fired. In such a scenario when the detached object is not modified we can use the lock() method to make it persistent again.
public static void reattachUsingLock() {
    Session session1 = sessionFactory.openSession();
    Entity entity = (Entity) session1.load(Entity.class, 1);
    //The object is now persistent
    session1.close();
    //The object is now detached
    //changes made at this point has no impact        
    Session session2 = sessionFactory.openSession();
    Transaction transaction = session2.beginTransaction();
    session2.lock(entity, LockMode.NONE);
    //The object is now persistent again
    System.out.println("data is " + entity.getData());
    transaction.commit();
    session2.close();
}
When the lock method is called, the detached object is added to the persistence context. However no dirty check happens. So if the object was modified when it was in the detached state, Hibernate is not aware of it.
This can be verified by the below code. The detached instance was modified and then associated with a second session using lock.
public static void reattachUsingLock() {
    Session session1 = sessionFactory.openSession();
    Entity entity = (Entity) session1.get(Entity.class, 1);
    session1.close();
    //The object is now detached
    //changes made at this point has no impact    
    entity.setData("Changed Data");            

    Session session2 = sessionFactory.openSession();
    Transaction transaction = session2.beginTransaction();
    session2.lock(entity, LockMode.NONE);
    //The object is now persistent again
    transaction.commit();
    session2.close();
}
This object will not be updated. The reason being when u add an object with lock, the session assumes that this object is in the same state as an object loaded from the database. Hence unless you make some changes to the now persistent object, no update queries will be fired. Now consider this code:
public static void reattachUsingLockandSave() {
    Session session1 = sessionFactory.openSession();
    Entity entity = (Entity) session1.load(Entity.class, 1);
    System.out.println(entity.getData());
    //The object is now persistent
    session1.close();
    //The object is now detached
    //changes made at this point has no impact    
    String originalData = entity.getData(); 
    entity.setData("Changed Data");            
    Session session2 = sessionFactory.openSession();
    Transaction transaction = session2.beginTransaction();
    session2.lock(entity, LockMode.NONE);
    entity.setData(originalData);
    //The object is now persistent again and also dirty
    transaction.commit();
    session2.close();
}
I have called a setter to the reattached object (in reality I have only restored the objects original value - making it an exact copy of the db record). But calling the setter tells Hibernate that a persistent object changed and it will schedule it for an update.
Unless the object gets modified (while in persistent state) or an explicit update call is made, Hibernate will not schedule it for an update query.
There is also a third technique - session.merge() which is capable of making a detached object persistent again.

1 comment: