Hibernate version: 3.0.5
I am interested (out of curiosity) in your opinions about an asymmetry between saving and deleting detached objects. Concretely, in the context of Container Managed Transactions in J2EE Session Beans, there is a difference between session.merge() and session.delete() in the way a StaleObjectStateException is propagated. When deleting an object that has been deleted by another transaction, the StaleObjectStateException is thrown when the TM calls beforeCompletion() on Hibernate's FlushingEventListener.
Conversely, when calling merge() with a stale object, the merge() method detects the staleness of the object and throws a StaleObjectStateException immedediately, long before the TM comes into play.
This is logical because merge() but not delete() is explicitly defined as carrying the state of a detached object to a persistent object. But it has a somewhat unfortunate side-effect. In our J2EE projects, we want to hide Hibernate behind session facades. Web clients only see interfaces of persistent (but detached) objects, and talk to a remote facade for managing their life cycle. By our standards, web applications must handle concurrent update situations. To that end we define a checked exception, ConcurrentUpdateException. Now we could implement a session facade method for saving User objects as follows:
Code:
public User saveUser(User user) throws ConcurrentUpdateException {
if (user == null) {
throw new IllegalArgumentException("No user specified");
}
Session s = HibernateUtil.getFactory().getCurrentSession();
try {
return (User) s.merge((UserImpl)user);
} catch(StaleObjectStateException e) {
this.sessionContext.setRollbackOnly();
throw new ConcurrentUpdateException(e);
}
}
When the supplied object is stale, this method sets the transaction to rollback and informs the client of the problem. A similar approach with deleteUser() does not work:
Code:
public void deleteUser(User user) throws ConcurrentUpdateException {
if (user == null) {
throw new IllegalArgumentException("No user specified");
}
Session s = HibernateUtil.getFactory().getCurrentSession();
try {
s.delete(user);
} catch(StaleStateException e) {
//Never reached!
}
}
The exception is never catched, because it is thrown from inside beforeCompletion() which is called by the TM after the above method returns. The TM propagates the StaleStateException to the client, wrapped up in a RemoteException.
Of course this is not insuperable: we simply check manually that the object still exists by substituting the following for the try/catch block in the previous:
Code:
UserImpl u2 = (UserImpl)s.get(UserImpl.class, user.getId());
if (u2 != null) {
s.delete(u2);
} else {
this.sessionContext.setRollbackOnly();
throw new ConcurrentUpdateException("User object has been deleted by someone else");
}
It's just not pretty (symmetrical). So my question is: why isn't there a special delete method for detached objects alongside delete(). Or better still: would be possible and desirable to change the semantics of delete() so that the difference becomes transparent? I would be interested in any thoughts you may want to share. Thanks,
Jan Voskuil