Wednesday, April 14, 2010

Hibernate Object States

Hibernate object states

* Transient - an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session. It has no persistent representation in the database and no identifier value has been assigned. Transient instances will be destroyed by the garbage collector if the application does not hold a reference anymore. Use the Hibernate Session to make an object persistent (and let Hibernate take care of the SQL statements that need to be executed for this transition).

* Persistent - a persistent instance has a representation in the database and an identifier value. It might just have been saved or loaded, however, it is by definition in the scope of a Session. Hibernate will detect any changes made to an object in persistent state and synchronize the state with the database when the unit of work completes. Developers do not execute manual UPDATE statements, or DELETE statements when an object should be made transient.

* Detached - a detached instance is an object that has been persistent, but its Session has been closed. The reference to the object is still valid, of course, and the detached instance might even be modified in this state. A detached instance can be reattached to a new Session at a later point in time, making it (and all the modifications) persistent again. This feature enables a programming model for long running units of work that require user think-time. We call them application transactions, i.e., a unit of work from the point of view of the user.

Detached Objects & re-attaching them:

Many applications need to retrieve an object in one transaction, send it to the UI layer for manipulation, then save the changes in a new transaction. Applications that use this kind of approach in a high-concurrency environment usually use versioned data to ensure isolation for the "long" unit of work.

Hibernate supports this model by providing for reattachment of detached instances using the Session.update() or Session.merge() methods:
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);

// in a higher layer of the application
cat.setMate(potentialMate);

// later, in a new session
secondSession.update(cat); // update cat
secondSession.update(mate); // update mate

If the Cat with identifier catId had already been loaded by secondSession when the application tried to reattach it, an exception would have been thrown.

Use update() if you are certain that the session does not contain an already persistent instance with the same identifier. Use merge() if you want to merge your modifications at any time without consideration of the state of the session. In other words, update() is usually the first method you would call in a fresh session, ensuring that the reattachment of your detached instances is the first operation that is executed.

The application should individually update() detached instances that are reachable from the given detached instance only if it wants their state to be updated. This can be automated using transitive persistence. See Section 10.11, “Transitive persistence” for more information.

The lock() method also allows an application to reassociate an object with a new session. However, the detached instance has to be unmodified.

//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.lock(pk, LockMode.UPGRADE);

Note that lock() can be used with various LockModes. See the API documentation and the chapter on transaction handling for more information. Reattachment is not the only usecase for lock().

saveOrUpdate() method

ither saves a transient instance by generating a new identifier or updates/reattaches the detached instances associated with its current identifier.

// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catID);

// in a higher tier of the application
Cat mate = new Cat();
cat.setMate(mate);

// later, in a new session
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)

The usage and semantics of saveOrUpdate() seems to be confusing for new users. Firstly, so long as you are not trying to use instances from one session in another new session, you should not need to use update(), saveOrUpdate(), or merge(). Some whole applications will never use either of these methods.

Appropriate usage scenarios:

Usually update() or saveOrUpdate() are used in the following scenario:

* the application loads an object in the first session
* the object is passed up to the UI tier
* some modifications are made to the object
* the object is passed back down to the business logic tier
* the application persists these modifications by calling update() in a second session

saveOrUpdate() does the following:

* if the object is already persistent in this session, do nothing
* if another object associated with the session has the same identifier, throw an exception
* if the object has no identifier property, save() it
* if the object's identifier has the value assigned to a newly instantiated object, save() it
* if the object is versioned by a or , and the version property value is the same value assigned to a newly instantiated object, save() it
* otherwise update() the object

merge() is very different:

* if there is a persistent instance with the same identifier currently associated with the session, copy the state of the given object onto the persistent instance
* if there is no persistent instance currently associated with the session, try to load it from the database, or create a new persistent instance
* the persistent instance is returned
* the given instance does not become associated with the session, it remains detached

1 comment: