4

What would be the right and easy way to update the collection if it's mapped with all-delete-orphan by it's "parent" object?
By updating I don't mean just adding/removing items from collection, but also updating the values of item properties (of course for those items that were previously in the collection).

Scenario is there's a Parent object that has a collections of Child objects and there's one form by which users can edit the Children collection - add/remove children but also edit the children's properties (on the same form).

Basically I want something like this:

Parent parent = session.get(Parent.class, parentUI.getId());
parent.setChildren(parentUI.getChildren());  // parentUI is a DTO
session.saveOrUpdate(parent);

This doesn't work and I understand the reasons why, but as it seems to me this should be a very common situation when using hibernate and developing UI applications, so I'm seeking for a (by the book) solution.

I'm using hibernate 3.6.10 and XML based configuration.
Here's the relevant mapping (I'm using ArrayList for storing children collection):

<list name="children" cascade="all, delete-orphan">
    <key column="parent_id" not-null="true"/>
    <list-index column="ordinal" />
    <one-to-many class="Child" />
</list>

If it matters child objects also have collections mapped in the same way but I don't think it's relevant since that is a problem equal to the one described.

Btw. I've lost a whole day on this and of course checked tens of very similar questions to this, but haven't found a reasonable solution or a pattern for solving this. Perhaps I'm missing something?

Josip Maslac
  • 270
  • 3
  • 8
  • try clearing the children list and then calling the 'setChildren' method – Dev Blanked Nov 27 '13 at 16:48
  • parent.setChildren(parentUI.getChildren()); If you want the detached objects to be saved, you can try merge() method. This might delete existing values, use it with caution. – Zeus Nov 27 '13 at 18:27
  • Use saveOrUpdate when you pull the data from the db in the current sesssion and trying to update the values in the same session context. – Zeus Nov 27 '13 at 18:29
  • @Zeus unfortunately however I try (merge or saveOrUpdate) I get "org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance:".For which I do understand the reason why it is happening, – Josip Maslac Nov 28 '13 at 10:28
  • @Dav Blanked that won't work - NonUniqueObjectException is thrown since in the case of updating existing children there can be two instances with same identity - one loaded from DB, and the one from UI (it doesn't matter if the collection is cleaned first - child objects are still bound to Hibernate session) – Josip Maslac Nov 28 '13 at 10:38
  • I think you have to add more cascade types to update, add save_update / merge. – Zeus Dec 01 '13 at 03:35
  • @Zeus I've tried that (ie. cascade="all, delete-orphan, merge") but with no success – Josip Maslac Dec 04 '13 at 16:01
  • Its wierd that it is not working, may be you have to query for all the children and save it without the involvement of the parent object. Last time when I was working with JPA, there was a property on the column annotation 'updatable' which used to stop all the updates that I do on the child object even when I have cascade set on it to all. You may want to check you config(On parent) for this type of setting. – Zeus Dec 05 '13 at 19:52

1 Answers1

1

Well since I didn't find a by the book solution I've first solved this manually by creating a custom method on my parent object parent.updateChildren(parentUI.getChildren()) which iterates through the old collection, compares it to the updated one from the UI and updates it manually. Needless to say it's far from ideal.

Then a colleague of mine (Zoran Regvart) gave me an witty idea (considering the technologies I'm using ie. Spring MVC) for improving things a little.
Idea is that, in case of submitting a form which updates the parent, before spring (web) binder does his work we can load the collection from the DB and set it to the command object (parentUI) thus "planting" it to be updated by the spring binder (in other words delegating the collection update process to him - thing that he does anyways).

Here is the (pseudo)code:

@ModelAttribute("parentUI")
public ParentUI initParentUI(HttpServletRequest p_request) {
    ParentUI parentUI = new ParentUI();
    String parentId = p_request.getParameter("id");
    if (parentId != null) { // indicates a form submit
        Parent parent = session.get(Parent.class, parentUI.getId());
        parentUI.setChildren(parent.getChildren());
    }
    return parentUI;
}

This way when spring binder does his work he will be updating the collection that is already bounded to Hibernate session.

This solution also isn't something that I'm too happy with and I haven't tested in many scenarios but for my current needs (for me) it's certainly somewhat of an improvement.

UPDATE: this workaround is convenient only in the case of adding new children or editing their properties. For cases of deleting children or changing their order it better avoided since it ads complexity.

Josip Maslac
  • 270
  • 3
  • 8