All entries for Tuesday 25 April 2006
April 25, 2006
Hibernates bizarre interpretation of inverse ;)
Writing about web page hibernate.org
So my colleague asked me about the exact semantics of hibernate's "inverse" keyword which is surely the worst choice of word for a simple concept.
Essentially "inverse" indicates which end of a relationship should be ignored, so when persisting a parent who has a collection of children, should you ask the parent for its list of children, or ask the children who the parents are?
Why does this matter? Well if you are stupid then you might do something like:
Parent parentA = new Parent();
Parent parentB = new Parent();
Child child = new Child();
parentA.getChildren().add(child);
child.setParent(parentB);
how should hibernate persist this situation? For unidirectional one–to–many it is trivial; only one end of the relationship is modelled (there is only parent.addChild(), not child.getParent()), but when it is bidirectional (parent.getChild and child.getChildren) you need to indicate whether the one–to–many is inverse or not.
What does it mean to be inverse? It informs hibernate to ignore that end of the relationship. If the one–to–many was marked as inverse, hibernate would create a child–>parentB relationship (child.getParent). If the one–to–many was marked as non–inverse then a child–>parentA relationship would be created.
Or at least that is what you would think :) First moan is why use the word "inverse"? By setting it to true you are effectively telling hibernate to ignore it. Setting something to true to exclude something is reverse logic and is incredibly unintuitive. Maybe "owner" would be a better term?
The second moan is that hibernate isn't consistent. If the parent–>child is not inverse (i.e. parent.getChildren will be used to define the relationship) then you would expect calling child.setParent to have no effect. This is not the case. Calling child.setParent does create the relationship. If you end up with the idiotic case I proposed earlier, than hibernate will give precendece to the parent.getChildren (as it should), but to be consistent child.setParent(parentA) should not create the relationship.
It is such a simple concept, but so many people seem to struggle with it. I really don't think hibernates terminology is very helpful.
Simply put; if you set inverse=true on a one–to–many then child.getParent will be called, if inverse=false then parent.getChildren will be called.
Of course, good developers will not run into this problem because they will all ensure that parents have appropriate accessors (parent.addChild, parent.removeChild etc.) :)
The invertedness (?) of the relationship does have quite an impact on performance and schema design; if the one–to–many is not inverse (default) then everytime you create a child, hibernate will execute 2 statements, one to create the child, one to update the child with the foreign key of the parent. Foreign key constraints/not null constraints? hah, who needs those anyway :) Hibernate will also instantiate the collection everytime you add a child….not sure why. Setting inverse to true will fix both of these annoyances/downright ridiculous design decisions.
One last note; cascade and inverse are completely orthogonal. Cascade simply tells hibernate how to reach objects. If I set inverse=true on a one–to–many then hibernate will not use parent.getChildren to define the relationship. If I set cascade to save, or all then hibernate will call parent.getChildren to see if there are any new children. Setting inverse=true and calling child.setParent will have absolutely no effect because hibernate won't know about your new child :)
So summary:
– always provide parent.addChild which updates both ends of the relationship
– always set inverse to true on bidirectional one–to–many
– make sure you understand the difference between cascades and inverse ;)
P.S. Version 3.1.2 was used.
Code used for inverse relationship
<?xml version="1.0"?>
<!DOCTYPE hibernate–mapping PUBLIC
"–//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate–mapping–3.0.dtd">
<hibernate–mapping>
<class name="uk.ac.warwick.Child" table="child">
<id name="id"
column="id"
type="java.lang.String"
unsaved–value="null">
<generator class="org.hibernate.id.UUIDHexGenerator"/>
</id>
<many–to–one name="parent"
class="uk.ac.warwick.Parent"
column="parent_id"/>
</class>
</hibernate–mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate–mapping PUBLIC
"–//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate–mapping–3.0.dtd">
<hibernate–mapping>
<class name="uk.ac.warwick.Parent" table="parent">
<id name="id"
column="id"
type="java.lang.String"
unsaved–value="null">
<generator class="org.hibernate.id.UUIDHexGenerator"/>
</id>
<bag name="children" inverse="true" cascade="save–update">
<key column="parent_id"/>
<one–to–many class="uk.ac.warwick.Child"/>
</bag>
</class>
</hibernate–mapping>
package uk.ac.warwick.inverse;
import junit.framework.TestCase;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import uk.ac.warwick.Child;
import uk.ac.warwick.Parent;
public final class TestRelationships extends TestCase {
public void testChildrenOfParentAreIgnored() {
Configuration config = new Configuration();
config.addResource("uk/ac/warwick/inverse/Child.hbm.xml");
config.addResource("uk/ac/warwick/inverse/Parent.hbm.xml");
config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
config.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
config.setProperty("hibernate.hbm2ddl.auto", "create–drop");
config.setProperty("hibernate.show_sql", "true");
config.setProperty("hibernate.connection.url", "jdbc:hsqldb:.");
config.setProperty("hibernate.connection.username", "sa");
SessionFactory factory = config.buildSessionFactory();
Session session = factory.openSession();
Parent parentA = new Parent();
session.save(parentA);
session.flush();
/**
* Because parent–>child is inverse, adding the child to the parent
* will not associate it with the parent.
*
* Cascade will save it though.
*/
Child childA = new Child();
parentA.getChildren().add(childA);
session.save(parentA);
session.flush();
session.evict(childA);
assertEquals("cascade works so child should be saved",
1,
session.createCriteria(Child.class).list().size());
childA = (Child) session.load(Child.class, childA.getId());
assertNull("childAs parent should not be set!",
childA.getParent());
session.evict(parentA);
parentA = (Parent) session.load(Parent.class, parentA.getId());
assertTrue("but it shouldnt update the children of parentA",
parentA.getChildren().isEmpty());
childA.setParent(parentA);
session.save(childA);
session.flush();
/**
* Because child–>parent is not inverse, setting the parent
* will create the association
*/
childA.setParent(parentA);
session.save(childA);
session.flush();
session.evict(childA);
childA = (Child) session.load(Child.class, childA.getId());
assertEquals("childAs parent should now be set!",
parentA,
childA.getParent());
session.evict(parentA);
parentA = (Parent) session.load(Parent.class, parentA.getId());
assertFalse("and parentA should have children",
parentA.getChildren().isEmpty());
}
}
Code used for non inverse relationship
<?xml version="1.0"?>
<!DOCTYPE hibernate–mapping PUBLIC
"–//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate–mapping–3.0.dtd">
<hibernate–mapping>
<class name="uk.ac.warwick.Child" table="child">
<id name="id"
column="id"
type="java.lang.String"
unsaved–value="null">
<generator class="org.hibernate.id.UUIDHexGenerator"/>
</id>
<many–to–one name="parent"
class="uk.ac.warwick.Parent"
column="parent_id"/>
</class>
</hibernate–mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate–mapping PUBLIC
"–//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate–mapping–3.0.dtd">
<hibernate–mapping>
<class name="uk.ac.warwick.Parent" table="parent">
<id name="id"
column="id"
type="java.lang.String"
unsaved–value="null">
<generator class="org.hibernate.id.UUIDHexGenerator"/>
</id>
<bag name="children" inverse="false" cascade="save–update">
<key column="parent_id"/>
<one–to–many class="uk.ac.warwick.Child"/>
</bag>
</class>
</hibernate–mapping>
package uk.ac.warwick.non_inverse;
import junit.framework.TestCase;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import uk.ac.warwick.Child;
import uk.ac.warwick.Parent;
public final class TestRelationships extends TestCase {
public void testParentOfChildrenAreIgnored() {
Configuration config = new Configuration();
config.addResource("uk/ac/warwick/non_inverse/Child.hbm.xml");
config.addResource("uk/ac/warwick/non_inverse/Parent.hbm.xml");
config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
config.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
config.setProperty("hibernate.hbm2ddl.auto", "create–drop");
config.setProperty("hibernate.show_sql", "true");
config.setProperty("hibernate.connection.url", "jdbc:hsqldb:.");
config.setProperty("hibernate.connection.username", "sa");
SessionFactory factory = config.buildSessionFactory();
Session session = factory.openSession();
Parent parentA = new Parent();
session.save(parentA);
session.flush();
/**
* Even though child–>parent is inverse, setting the parent
* will associate it with the parent.
*/
Child childA = new Child();
childA.setParent(parentA);
session.save(childA);
session.flush();
session.evict(childA);
assertEquals("cascade works so child should be saved",
1,
session.createCriteria(Child.class).list().size());
childA = (Child) session.load(Child.class, childA.getId());
assertNotNull("childAs parent will be set!",
childA.getParent());
session.evict(parentA);
parentA = (Parent) session.load(Parent.class, parentA.getId());
assertFalse("and it will update the children of parentA",
parentA.getChildren().isEmpty());
}
}