views:

465

answers:

3

Hi all,

I can't delete a child object from the database. From the org.apache.struts.action.Action.execute() method, I am removing the child from the parent's List, and also calling session.delete(child). I've simplified the code below and only included what I believe to be relavent.


Hibernate Mapping

<class 
    name="xxx.xxx.hibernate.Parent" 
    table="parent">

    ...

    <list
        name="children"
        cascade="all,delete-orphan"
        lazy="true"
        inverse="true">

        <key column="parent_id"/>
        <index column="list_index"/>
        <one-to-many class="xxx.xxx.hibernate.Child"/>
    </list>
</class>

<class 
    name="xxx.xxx.hibernate.Child" 
    table="child">

    ...

    <many-to-one
        name="parent"
        class="xxx.xxx.hibernate.Parent"
        not-null="true"
        column="parent_id" />

</class>


Excerpt from execute() method

Transaction tx = session.beginTransaction();  //session is of type org.hibernate.Session

try {
    Parent parent = (Parent) session.get(Parent.class, getParentId());

    Iterator i = form.getDeleteItems().iterator();  //form is of type org.apache.struts.action.ActionForm
    while(i.hasNext()){
        Child child = (Child) i.next();
        session.delete(child);
        parent.getChildren().remove(child); //getChildren() returns type java.util.List
    }

    session.saveOrUpdate(parent);
    tx.commit();
} ...


I've tried with only session.delete(child); and I've tried with only parent.getChildren().remove(child); and with both lines, all without success. There are no errors or thrown exceptions or anything of the sort. I'm sure this code gets called (I've even used System.out.println(); to trace what's happening), but the database isn't updated. I can add children using similar code, edit non-collection properties of existing children, edit the parent's properties, all of that works, just not deleting!

According to the Hibernate FAQ I'm doing the mapping right, and according to this SO question I've got the right logic. I've looked all over the internet and can't seem to find anything else.

What am I doing wrong? Please help! Thanks.


Notes on versions

Everything is a few years old:

  • Java 1.4.2
  • SQL Server 2005
  • Hibernate 3.0.5
  • Struts 1.2.7
  • Apache Tomcat 5.0.28
+1  A: 

I'm not sure what causes this behavior in hibernate, you can get going by loading the Child first. Separately deleting the Child is not nessesary. Updated code should look like;

Transaction tx = session.beginTransaction();  //session is of type org.hibernate.Session

try {
    Parent parent = (Parent) session.get(Parent.class, getParentId());

    Iterator i = form.getDeleteItems().iterator();  //form is of type org.apache.struts.action.ActionForm
    while(i.hasNext()){
        Child child = (Child) session.get(Chile.class, ((Child) i.next()).getChildId());
        parent.getChildren().remove(child); //getChildren() returns type java.util.List
    }

    session.saveOrUpdate(parent);
    tx.commit();
} ...

show the SQL generated by Hibernate

<property name="show_sql">true</property>
<property name="format_sql">true</property>

Edit:

Check out this Chapter 10. Working with objects

n002213f
this will work, but I think loading each object anew should be avoided.
Bozho
@Bozho: Why do you think loading each object anew should be avoided? What makes your solution better than this?
Peter Di Cecco
if the session is fresh, this would mean N queries to the database for fetching the items.
Bozho
+1  A: 

If you haven't overridden the equals() method, the entity is probably not found in the list, because it has been detached, and is now a different instance. That's why the remove isn't working. Then even if the delete works, the objects are re-cascacde because they still exist in the collection. Here's what to do:

  • either override the equals() (and hashCode()) method(s), using either the id (easy) or some sort of busines key (more appropriate) (search stackoverflow for tips for overrideing these two metods), and leave only getChildren().remove(child)
  • Iterate over the collection of children in the first loop, like this:

    Iterator<Child> i = form.getDeleteItems().iterator();
    while(i.hasNext()){
        Child child = i.next();
        for (Iterator<Child> it = parent.getChildren().iterator();) {
             if (child.getId().equals(it.next().getId()) {
                 it.remove(); // this removes the child from the underlying collection
             }
        }
    }
    
Bozho
it won't work even after overriding equals and hashCode. Removing the object via the iterator is not nessesary, remove does the same faster.
n002213f
why do you think so? I think you are wrong in both statements :)
Bozho
Is the Child's Id populated properly in the Form?
EJB
are you asking me? :)
Bozho
@Erik-Jan: Yes.
Peter Di Cecco
Peter, did you try my suggestion? Did it work?
Bozho
@Bozho: Unfortunately, I didn't have a chance to try it yet. I have been putting out other fires all day at work. I will probably try it on the weekend, or early next week. Don't worry, once I try out some things I'll up vote and select an answer.
Peter Di Cecco
@Bozho - i tried it and didn't work.
n002213f
Sorry about the delayed response, but I'm the only one on the team (for development and support!) and sometimes it gets pretty crazy. Thanks so much, this helped a lot. I ended up also having to change the way getDeletedItems was filled, and a few other things, but in the end this basic technique did the trick.
Peter Di Cecco
A: 

In this case, the Child class is the owner of the inverse relation, Hibernate will look at the parent reference of the child to determine whether the relation is still there. Since you don't set the parent to null, the relation exists and the child may not be deleted. Try doing

parent.getChildren().remove(child);
child.parent = null; 
session.delete(child);

Also remove the not-null="true" from the parent property mapping.

The best thing to do when working with inverse associations, is to update both sides in Java code, that way you can continue working with the objects in memory and you don't have to worry about which side owns the relation.

A similar situation is discussed here: http://simoes.org/docs/hibernate-2.1/155.html

Danny Groenewegen
Delete orphan will handle the removal of child even if you omit the 2nd line. And this won't work because remove(child) won't remove anything
Bozho
Indeed, I thought that inverse="true" would prevent the removal, but http://docs.jboss.org/hibernate/core/3.3/reference/en/html/example-parentchild.html states that cascades are still processed when inverse="true".
Danny Groenewegen