-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 
Author Message
 Post subject: Understanding cascade
PostPosted: Tue Nov 15, 2011 12:28 pm 
Newbie

Joined: Tue Nov 15, 2011 12:21 pm
Posts: 3
Hi,
im new in hibernate and im trying to understand the cascade concept.

I studied the hibernate documentation, but some things are unclear to me:

1) How to use cascade in a bidirectional one-to-many Relation:

So lets say, i have a Parent, and a parent can have zero or more Children.

The behaviour should be:
- if a Parent-object is deleted, all of its Children should be deleted too.
- It should be possible, to delete a single Child. So only the relation/connection between parent and this single Child should be deleted, but not the Parent.

So my Parent config file would look like this:

Code:
<class name="Parent" table="Parent">
    <set name="children" table="Child" fetch="select">
            <key>
                <column name="Parent_id" not-null="true" />
            </key>
            <one-to-many class="Child" />
       </set>
</class>



the Child config file:
Code:
<class name="Child" table="Child">
    <many-to-one name="parent" class="Parent" fetch="select">
            <column name="Parent_id" not-null="true" />
    </many-to-one>
</class>



How do i specify the cascade behavior (in Child or Parent)?
And how does my DAO delete this Child?
I guess by configurating cascade correctly its quite simple,
so that a simple delete like this

Code:
public void deleteChild(Child child){
   Transaction tx = session.beginTransaction();   
   session.delete(childObject);
   tx.commit();
}


is enough to remove also the connection in the parent object

or does the DAO need to remove the connection by hand in this manner:

Code:
public void deleteChild(Child child){
   Transaction tx = session.beginTransaction();   
   Parent p = child.getParent();
   p.getChildren().remove(p);
   session.delete(childObject);
   session.update(p);
   tx.commit();
}




2) My second Question is similar to the first, but this time i use a bidirectional many-to-many relation:
Lets say :
- A class User: and a User can be member of zero or more Groups
- A Group can contain zero or more Users

So if i delete a User, than only the User and the relation to the Group should be deleted and not the Groups, in which this User was member.
And vice versa,
if i delete a Group, than only the Group and the relation to the User should be deleted, but not the Users that were member of this Group.


The config file for User:
Code:
<class name="User" table="User">
   <set name="groups" table="UserGroupMember" inverse="true">    <!-- inverse = true, since i want that the Group adds User as member -->   
      <key column="User_id" />         
      <many-to-many column="group_id" class="Group"/>   
   </set>
</class>


and the config for the Group:
Code:
<class name="Group" table="Groups">
   <set name="users" table="UserGroupMember" inverse="false">       
         <key column="Group_id" />         
         <many-to-many column="User_id" class="User" />   
   </set>
</class>



How do i set cascade in this case?
And what about the DAO?

Is a simple delete enough or does the relations need to be deleted by hand like this:

Code:
public void deleteGroup(Group group)
{
   Transaction tx = session.beginTransaction();
   for (User u: group.getUsers())
   {
      u.getGroups().remove(group);
      session.update(u);
   }
   
   group.getUsers().clear();
   session.update(group);
   session.delete(group);
   tx.commit();
}


Top
 Profile  
 
 Post subject: Re: Understanding cascade
PostPosted: Tue Nov 15, 2011 12:50 pm 
Newbie

Joined: Thu Aug 13, 2009 1:23 am
Posts: 3
Location: Israel
Hi sockeqwe.

For your 1st question:

Add the following attribute to the "children" one-to-many in Parent.hbm.
Code:
cascade="all-delete-orphan"
Hibernate will automatically delete from the DB every Child that has been removed from the collection. It will also save automatically every Child that has been changed or added.
No need to add/delete a Child specifically in the DAO, all you'll have to do is session.saveOrUpdate(parent).

You can take a look on 2 interesting posts that I've wrote about this topic: http://itziksaban.blogspot.com/2011/11/nhibernate-bi-directional-use-it.html

For your 2nd question:

If you don't want a Group to be deleted whenever a User is being deleted and vice versa - simply do not add a cascade to the many-to-many element in both of them. This will delete the connection in the DB but not the connected elements (User or Group)

Best Regards,

Itzik Saban.


Top
 Profile  
 
 Post subject: Re: Understanding cascade
PostPosted: Wed Nov 16, 2011 5:31 pm 
Newbie

Joined: Tue Nov 15, 2011 12:21 pm
Posts: 3
Thank you for your answer.

I tried to implemement the one-to-many example:
Code:
<class name="Parent" table="Parent">
    <id name="id" type="int" column="id">
       <generator class="increment" />
    </id>

    <set name="children" table="Child" fetch="select" cascade="all-delete-orphan">
            <key>
                <column name="Parent_id" not-null="true" />
            </key>
            <one-to-many class="Child" />
    </set>

</class>


<class name="Child" table="Child">
   
   <id name="id" type="int" column="id">
       <generator class="increment" />
    </id>
    <many-to-one name="parent" class="Parent" fetch="select">
            <column name="Parent_id" not-null="true" />
    </many-to-one>
</class>


So i inserted an example parent with two children:
Code:
Transaction tx = session.beginTransaction();
        
            Parent p = new Parent();
            Child c1 = new Child();
            Child c2 = new Child();
            c1.parent = p;
            c2.parent = p;
            
            p.children.add(c1);
            p.children.add(c2);
            
            session.save(p);
        
tx.commit();


it worked!

Now i want to delete a Parent incl. children:
Code:
Transaction tx = session.beginTransaction();
         session.delete(parent);
tx.commit();


That throws an Exception:
org.hibernate.exception.ConstraintViolationException: Column 'Parent_id' cannot be null
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'Parent_id' cannot be null

I understand, what that exception mean (hibernate deletes first the parent data record, and then the foreign key of the Child database table will be set to null, which is not allowed), but im suprised of that behaviour, since i set cascade="all-delete-orphan" in the config file.

I suppose to have an error in reasoning, do I ?


Top
 Profile  
 
 Post subject: Re: Understanding cascade
PostPosted: Wed Nov 16, 2011 6:08 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
There is an example in the Hibernate documentation that include most of what you need to know: http://docs.jboss.org/hibernate/core/3. ... child.html


Top
 Profile  
 
 Post subject: Re: Understanding cascade
PostPosted: Thu Nov 17, 2011 11:46 am 
Newbie

Joined: Tue Nov 15, 2011 12:21 pm
Posts: 3
Thank you for your answer.
Finally I managed to implement a bidirectional one-to-many relation, with this config for the Parent class:

Code:
<class name="Parent" table="Parent">
...
<set name="children" table="Child" fetch="select" cascade="all-delete-orphan" inverse="true">
...


invere=”true” is the the salient point.




But I'm still facing problems with the bidirectional many-to-many relation.

I tried it that way:
Code:
<class name="Group" table="Groupe">
   <id name="id" type="int" column="id">
       <generator class="increment" />
    </id>
    <set name="users" table="UserGroupMember">       
         <key column="Group_id" />         
         <many-to-many column="User_id" class="User" />   
   </set>
</class>


<class name="wrong.many.to.many.User" table="User">
    <id name="id" type="int" column="id">
       <generator class="increment" />
    </id>
    <set name="groups" table="UserGroupMember" inverse="true" cascade="all-delete-orphan">    <!-- inverse = true, since i want that the Group adds User as member -->   
      <key column="User_id" />         
      <many-to-many column="Group_id" class="wrong.many.to.many.Group" />   
   </set>
</class>



With this config, creating an User, creating a Group and creating the relationship between them works fine.
Also deleting a Group and the corresponding relationship to User works,
but deleting a User will cause problem, since cascade="all-delete-orphan" let also delete the Groups, where the User was assigned to.

Code:
public void deleteUser(User u)
{
Transaction tx = session.beginTransaction();
for (Group g: u.groups)
         {
            g.users.remove(u);
            session.update(g);
         }
         
         u.groups.clear();
         session.update(u);
         
         session.delete(u);
         
      tx.commit();
}

I want to delete only the relationship and not the Groups, so cascade="all-delete-orphan" is the wrong flag (like expected). How do I solve this problem?

Whith removing cascade I get this exception:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`UserGroupMember`, CONSTRAINT `FK58E2090EFBE31FA5` FOREIGN KEY (`User_id`) REFERENCES `User` (`id`))

It's clear to me what this Exception will say to me, but i dont know why this exception is thrown, because i thought I could remove this database record with:

Code:
for (Group g: u.groups)
{
   g.users.remove(u);
   session.update(g);
}
         
u.groups.clear();
session.update(u);




If I remove inverse than deleting a user works, but i will face other problems like,
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1-2' for key 'PRIMARY'
by inserting a new User-Group Relation, because of:
Code:
public void assignUserToGroup(Group g, User u)
Transaction tx = session.beginTransaction();
u.groups.add(g);
g.users.add(u)
tx.commit();
}

Since ther is no inverse set, nobody is the owner of the relation, and both (User and Group) will insert into ther UserGroupMember Database this relation, which will cause the Excpetion.


A solution may be to create the relation via only one Entity:
Code:
public void assignUserToGroup(Group g, User u)
Transaction tx = session.beginTransaction();
u.groups.add(g);
//g.users.add(u)
tx.commit();

  System.out.println("CONTAIN"+ g.users.contain(u));     // Is false
}


That works (the relation is saved in the table UserGroupMember correctly and only once), but it brings other problems:
because g.users does not contain the User u.

And to solve this problem i could close and reopen the session:
Code:
public void assignUserToGroup(Group g, User u)
Transaction tx = session.beginTransaction();
u.groups.add(g);
//g.users.add(u)
tx.commit();

// UGLY HACK
session.close();
session = sessionFactory.openSession();
}


By reoping the session the relation will be loaded correctly next time, when you access the user object, but thats a very ugly hack.
There must be a better solution!

Any suggestion?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.