Hi Christian, no problem with the shouting :-) The trouble with being a newbie to Hibernate & double checking the stuff from the book against the documentation, is that the relationship stuff in Hibernate is quite complex when you start using inverse="true", cascades & extended transactions using multiple sessions (which I need to do as it is a web application I am porting). The thinking has to be quite different from EJB2 CMP moving to Hibernate.
I have put a page on our wiki at work containing the following information, which has helped us to understand more clearly. I wonder if it might be useful to have on the HIbernate site, or I can host it & provide a URL. I wonder if someone would be kind enough to check it for me & let me know if you thought it would be useful :-)
Thanks
Paul
many-to-many Hibernate relationship
Explanation of 'inverse="true"' from
http://www.hibernate.org/155.html
Quote:
'Inverse defines which side is responsible of the association maintenance. The side having inverse="false" (default value) has this responsibility (and will create the appropriate SQL query - insert, update or delete). Changes made to the association on the side of the inverse="true" are not persisted in DB.
Inverse attribute is not related in any way to the navigation through relationship. It is related to the way hibernate generate SQL queries to update association data.'
Because we have cascade="save-update" on the Child's relationship to the Parent, save will be called on the Parent, even when we only save the Child, therefore the primary (inverse="false") side of the relationship is saved & so all relationships are updated. Thus we can safely update either side of the relationship, as save will always be called on the primary relationship. (If we did not have the cascade="save-update" on the Child's relationship to the Parent we would have to ensure that we always saved on the Parent side of the relationship or the relationship table would not get updated.)
This can be tested by adding the following code to the test case
Code:
Child child2 = new Child("GRACE","CHILD2", new HashSet(), new HashSet());
child2.getParents().add(parent);
parent.getChildren().add(child2);
session = HibernateSessionFactory.getSession();
tx = session.beginTransaction();
session.saveOrUpdate(child2);// this should end up not being persisted
tx.commit();
session.close();
// check to see Child relationship to Parent wasn't persisted
session = HibernateSessionFactory.getSession();
loadedParent = (Parent) session.load(Parent.class,parentId);
session.close();
parentsFromChild = loadedParent.getChildren();
assertTrue(parentsFromChild.size() == 1);
assertFalse(parentsFromChild.contains(child2));
and modifying the Child mapping file by removing the
cascade="save-update" attribute.
Note that Hibernate Developer's Notebook end of page 71 - start of page 72 is incorrect, you do need to add to both sides of the relationship. The inverse="true" attribute has nothing to do with the pure Java releationships, it only relates to how the sql is generated to update the relationship tables It is also important to understand how to correctly implement equals() & hashCode() for use with Hibernate persistent classes. See
http://www.hibernate.org/109.htmlrelationship mapping sections from Parent & Child classesfrom Parent mapping file Code:
<set name="children" table="parent_child" cascade="all-delete-orphan">
<key column="child_id"/>
<many-to-many column="parent_id" class="Child"/>
</set>
from Child mapping file Code:
<set name="parents" table="parent_child" inverse="true" cascade="save-update">
<key column="parent_id"/>
<many-to-many column="child_id" class="Parent"/>
</set>
JUnit test methodCode:
public void testParentChild(){
try {
Session session = null;
Transaction tx = null;
try {
//create parent
session = HibernateSessionFactory.getSession();
tx = session.beginTransaction();
Parent parent = new Parent("DAD","PARENT1",new HashSet());
session.save(parent);
tx.commit();
session.close();
// add child
Child child = new Child("KATHERINE", "CHILD1",new HashSet(),new HashSet());
session = HibernateSessionFactory.getSession();
tx = session.beginTransaction();
child.getParents().add(parent);
parent.getChildren().add(child);
session.saveOrUpdate(parent);
tx.commit();
session.close();
//set ids for later
Integer parentId = parent.getId();
Integer childId = child.getId();
// check child is there
Set children = parent.getChildren();
assertTrue(children.size() == 1);
assertTrue(children.contains(child));
Iterator iterator = children.iterator();
Child childFromParent = null;
if (iterator.hasNext()) {
childFromParent = (Child) iterator.next();
}
assertEquals(child,childFromParent);
// now try changing Parent from Child side
Parent parentFromChild = null;
Set parentsFromChild = child.getParents();
iterator = parentsFromChild.iterator();
if (iterator.hasNext()) {
parentFromChild = (Parent) iterator.next();
}
assertNotNull(parentFromChild);
parentFromChild.setName("MUM");
session = HibernateSessionFactory.getSession();
tx = session.beginTransaction();
session.saveOrUpdate(parentFromChild);
tx.commit();
session.close();
// check name changed & persisted OK
session = HibernateSessionFactory.getSession();
Parent loadedParent = (Parent) session.load(Parent.class,parentId);
session.close();
assertEquals("MUM",loadedParent.getName());
// delete parent
session = HibernateSessionFactory.getSession();
tx = session.beginTransaction();
session.delete(parent);
tx.commit();
session.close();
// ensure parent & child have been deleted
session = HibernateSessionFactory.getSession();
try {
session.load(Parent.class, parentId);
fail("This object should no longer exist");
}
catch (HibernateException e) {
assertTrue(true);
}
try {
session.load(Child.class, childId);
fail("This object should no longer exist");
}
catch (HibernateException e) {
assertTrue(true);
}
session.close();
}
catch(HibernateException e){
tx.rollback();
throw e;
}
finally {
if(session != null){
session.close();
}
}
}
catch (HibernateException e) {
fail(e.getMessage());
}
}