There is a scenario when it could be useful with a "smarter" merge of cascaded collections.
Here is the scenario:
1. We load an object that has a number of relations
2. Displays it on a webpage and lets the user update it.
We keep the object id in the form as a hidden field. Only the main object is updated, children are left unchanged and not included on the web page.
3. When the user posts the form we let Tapestry recreate the object and load it with the attribs.
4. The new object is passed down to integration layer where Hibernate is told to saveOrUpdate it.
This usually works fine but when some other user have used the same object, thus loaded it into the session, we get the NonUniqueObjectException. We catch the exception and tries to merge the objects. This should theoretically work fine but we encounter one problem. The object has a few managed relations (cascade=all-delete-orphans, lazy=false) which not have been set since Tapestry recreated the object. The relations or objects in them have not been modified in any way. Setting a managed relation to null is, as we all know by now, not allowed and generates a
Quote:
"Don't dereference a collection with cascade="all-delete-orphan"
. Ok, that's according to specs, but couldn't merge just ignore the null relations and instead use the ones on the object already associated with the session?
Since it is unallowed to set cascaded relations to null one might reason that this should be the default behaviour of a merge operation, otherwise the semantics is more like a "replace".
The solution we use is to store the entire object graph in the HTTP session and then pass the whole object graph down to merge. This is doable but not optimal. We want to keep our HTTP session as small as possible for different reasons.
Example store method in integration layer
Code:
public void store(AbstractEntity entity) throws StingException {
Session session = ServiceUtils.getSession();
try {
try {
session.saveOrUpdate(entity);
} catch(HibernateException he) {
// saveOrUpdate failed, try merging instead
entity = (AbstractEntity)session.merge(entity);
}
} catch (Throwable e) {
throw new StingException(e);
}
}