Hibernate bidrectional relationship problem

Discussions

General J2EE: Hibernate bidrectional relationship problem

  1. Hi,

    I am having a problem saving objects which have a bidirectional one-to-many / many-to-one mapping. The parent object saves fine, but it tries to perform an update on the children collection rather than an insert.

    Here are my mappings:

    <hibernate-mapping>
        <class name="Parent" table="Parent">
            <id name="name" column="name">
                <generator class="assigned"></generator>
            </id>
    <set name="children" cascade="all" lazy="false">
                <key column="parentName"/>
    <one-to-many class="Child"/>
    </set>
        </class>
    </hibernate-mapping>

    <hibernate-mapping>
        <class name="Child" table="Children">
            <composite-id>
                <key-property name="parent" type="Parent" column="parentName"/>
                <key-property name="name" type="java.lang.String" column="name"/>
            </composite-id>
         </class>
    </hibernate-mapping>

    The parent has a Set of children whilst each child has a reference back to it's parent.

    Here's how I intend to use the relationship:

    Parent parent = new Parent("parent1");
    Set children = new HashSet();
    children.add(new Child(parent, "child1");
    children.add(new Child(parent, "child2");
    parent.setChildren(children);

    However, when I save the parent, an INSERT into the PARENT table is performed, but an UPDATE is attempted on the CHILD table, which fails since there isn't anything in the CHILD table to update!

    Does anyone have any idea why an INSERT isn't performed on the CHILD table rather than an UPDATE?
  2. Your mappingt is not correct since it doesn't explains to Hibernate the bidirectional association.

    Use attribute 'inverse="true"' in your <set> mapping first. Why are you disabling lazy collections? Is it really necessary?

    Second, use <key-many-to-one> element instead of <key-property> for mapping a parent property in child element mapping.

    If you discover, that you can use primary key column instead of composites, use it! And map composite key parts as simple properties. (the requirements for component with composite keys are described in hibernate_reference.pdf 5.1.5)

    Remember, that hibernate saves associations, that are made to noninverse end, so if you want to add child to parent, use smth like this:

    Parent p = new Parent();
    Child c = new Child();
    c.setParrent(p);
    p.getChildren().add(c);
    sess.save(c);

    You have no need to save parent since hibernate saves association between rows in child table only.

    You also probably would like to experiment with cascading in parent/child associations.

    Read hibernate_reference:
    Chapters 6.8, 6.5, 5.1.5, 7.4

    Good luck
  3. Thanks for the replies. I have added the <key-many-to-one> on the Child object, set lazy="true" but saving the object still fails.

    Here is my new mapping:

    <hibernate-mapping>
        <class name="Parent" table="Parent">
            <id name="name" column="name">
                <generator class="assigned"></generator>
            </id>
    <set name="children" cascade="all" inverse="true" lazy="true">
                <key column="parentName"/>
    <one-to-many class="Child"/>
    </set>
        </class>
    </hibernate-mapping>

    <hibernate-mapping>
        <class name="Child" table="Children">
            <composite-id>
                <key-many-to-one name="parent" class="Parent" column="parentName"/>
                <key-property name="name" type="java.lang.String" column="name"/>
            </composite-id>
         </class>
    </hibernate-mapping>


    I then use the following code, as you suggest:

      Parent p = new Parent();
      Child c = new Child();
      c.setParrent(p);
      p.getChildren().add(c);
      sess.save(c);

    Unfortunately, it doesn't save. I get the following error:

      "Could not synchronize database state with session"
      "net.sf.hibernate.HibernateException: SQL insert, update or delete failed (row not found)"

    Looking back through the debug logs, I noticed this line:

      "net.sf.hibernate.SQL - update Child where parent=? and name=?"

    Why is Hibernate trying to update this table? It should be performing an INSERT, since there is no data in the Child table at this point.

    Any help/guidance appreciated.
  4. Try check this:

    - equals() and hashCode() are correctly implemented in Child;
    - Child implements java.io.Serializable interface.

    The mapping is correct, its working (I've exclusively tested).

    Also try to save Parent object before saving Child. When the parent object has been created, it is not saved into table, so there is no representative row. Try this:

        Parent p = new Parent();
        p.setName("parent1");
        sess.save(p);
        sess.flush();
        
        Child c = new Child();
        c.setName("child1");
        c.setParent(p);
        p.getChildren().add(c);
        sess.save(c);
        sess.flush();
  5. the unsaved-value for the id field has to be 0 for hibernate to generate an insert statement .By default unsaved value is null and it will generate an update statement and it cannot find a row in the database with that id it will throw a "row not found" error .
  6. Your advice is quite correct in case of automatic id generation. But you see, here we have assigned id generator and should manually set id property (name of a parent in our case) before saving it. Moreover, in case of assigned generator, hibernate will never decide of calling INSERT or UPDATE -- it is impossible to determine, what to execute. Instead, we provide Hibernate with this information by calling save() or update() manually, but never saveOrUpdate() [hibernate reference, 5.1.4.5]. Of course, using parent name as primary key is not the best designing choice at all, but this can work.

    Also, notice that problem with UPDATE arises when we are trying to save child, not parent. And there we have composite primary key. But, whats worse, we have composite id (again, not a good design decision). The situation is so bad because Hibernate has only two strategies for unsaved-value in composite-id (and it's quite understandable): none and any. In first case, all objects treats as persistent, in second - as transient. Maybe the solution is to use <composite-id unsaved-value="any"> as opposed to default value 'none', but it may impact performance (or may not).

    Finally, I repeat, the second mapping introduced here works correctly on my workstation.
  7. Hand Mapping[ Go to top ]

    I believe that Yaroslav got it right, you need to tell Hibernate about the inverse relationship.

    However, the real problem is how to manage all this mapping when you've got lots of classes. Maybe you don't, but if you do then you might appreciate a more complete tool that uses the same POJO approach.

    Check out Versant Open Access - JDO It has a visual mapping tool that will eliminate the need for you to think about all those tags. You can visually select the data model pattern that you want to map to your object relationships. The following link gives you an overview with some great snap shots of the GUI mapping tool.

    http://www.versant.com/products/openaccess/openAccessJDO/

    Cheers,

    Robert Greene
    Versant Corporation
  8. Versant GUI[ Go to top ]

    That you have the option of "thinking about all those tags" does not preclude automation and there are already tools to automate Hibernate mappings -- not to mention new tools that are on the way. Capturing mappings in XML that can be used by GUI tools is the way to go, not proprietary skin-of-the-teeth hangers-on from the early 80's still trying to hawk expensive failures. IMO of course
  9. Hi,

    I've found an an alternative config that allows Saveorupdate() to correctly identify new objects (and therefore perform cascades correctly) when using assigned ids.

    The <version> tag creates a database column / property that logs version numbers whenever an object is saved or updated. This tag also contains a unsaved-value attribute. Using this should override id checking when determing whether an object isUnsaved.

    I'll post an example once I've got it working but here's some links:

    http://www.hibernate.org/116.html#A12
    http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=78&t=000071
  10. Hi ,

      I have problem it seems t be with mapping file.

    Lets consider the Parent and child tables which have a bidirectional one-to-many / many-to-one mapping

    Here are my mappings:

    <hibernate-mapping>
        <class name="Parent" table="Parent">
           <id name="name" column="name" type="string">
                <generator class="uuid.hex"/>
            </id>
           
         <set name="children" cascade="all" inver="true">
                <key column="parentName"/>
                 <one-to-many class="Child"/>
        </set>
        </class>
    </hibernate-mapping>

    <hibernate-mapping>
        <class name="Child" table="Children">
            <composite-id>
                <key-property name="name" type="long" column="childName"/>
                <key-many-to-one name="parent" type="Parent" column="parentName">
            </composite-id>
         </class>
    </hibernate-mapping>


    Parent parent = new Parent("parent1");


    Child c1= new Child();
    c1.setParent(parent);
    c1.setChildName(new Long(5545));

    Set childrens = new HashSet();
    childrens.add(c1);
    parent.setChildren(children);

    When i try to save the parent it saves fine but whe i try to fetch the parent objet it gives me the following exception

    Exception:
    org.hibernate.exception.DataException: could not initialize a collection:
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:75)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
    at org.hibernate.loader.Loader.loadCollection(Loader.java:1923)
    at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:71)
    at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:520)
    at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:60)
    at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1593)
    at org.hibernate.collection.AbstractPersistentCollection.forceInitialization(AbstractPersistentCollection.java:454)
    at org.hibernate.engine.StatefulPersistenceContext.initializeNonLazyCollections(StatefulPersistenceContext.java:791)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:228)
    at org.hibernate.loader.Loader.doList(Loader.java:2147)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2026)
    at org.hibernate.loader.Loader.list(Loader.java:2021)
    at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:94)
    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1483)
    at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:298)
    at com.effigent.engg.util.dao.hibernate.HibernateDAO.findEntitiesMatching(HibernateDAO.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:335)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:165)
    at $Proxy1.findEntitiesMatching(Unknown Source)
    at com.effigent.retail.domain.TransactionFacadeImpl.findRetailTransactionMatching(TransactionFacadeImpl.java:82)
    at com.effigent.retail.test.TestRetailTransactionFacade.testRetailTransactionUpdateSuccess(TestRetailTransactionFacade.java:212)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at junit.framework.TestCase.runTest(TestCase.java:154)
    at junit.framework.TestCase.runBare(TestCase.java:127)
    at junit.framework.TestResult$1.protect(TestResult.java:106)
    at junit.framework.TestResult.runProtected(TestResult.java:124)
    at junit.framework.TestResult.run(TestResult.java:109)
    at junit.framework.TestCase.run(TestCase.java:118)
    at junit.framework.TestSuite.runTest(TestSuite.java:208)
    at junit.framework.TestSuite.run(TestSuite.java:203)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
    Caused by: org.postgresql.util.PSQLException: Bad value for type long : 4028811b0ab1386d010ab138ad200007
    at org.postgresql.jdbc2.AbstractJdbc2ResultSet.toLong(AbstractJdbc2ResultSet.java:2548)
    at org.postgresql.jdbc2.AbstractJdbc2ResultSet.getLong(AbstractJdbc2ResultSet.java:1987)
    at org.postgresql.jdbc2.AbstractJdbc2ResultSet.getLong(AbstractJdbc2ResultSet.java:2210)
    at org.apache.commons.dbcp.DelegatingResultSet.getLong(DelegatingResultSet.java:239)


    If we observe the cause of the exception it says that : org.postgresql.util.PSQLException: Bad value for type long : 4028811b0ab1386d010ab138ad200007

    to resolve the issue i have modfied the child hbm to this :

    <hibernate-mapping>
        <class name="Child" table="Children">
            <composite-id>
                <key-property name="name" type="long" column="childName"/>
                <key-many-to-one name="parent" type="Parent" column="parentName">
            </composite-id>
         </class>
    </hibernate-mapping>

    In stead of using the type="long" for the key-propety i have modified the type as type="string" and my program started working fine by setting the childNae to string in the code too.

    Can any one explain me what could be the possible reasons for the exception .

    Thanks in advance
    Ramana
  11. I think the reason is data type cast problem. I am using Oracle10g, if the column type is Number, the mapped pojo field type must be java.lang.Long or long. If the column type is Integer, the mapped pojo field type should be java.lang.Integer or int. in your case, if column "childName" is varchar2, the mapped field type of pojo "Child" should be String. Just for your information. Richard