-->
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.  [ 4 posts ] 
Author Message
 Post subject: How to model a one to distinct, finite n relationship
PostPosted: Fri Feb 20, 2004 2:27 pm 
Newbie

Joined: Fri Feb 20, 2004 2:22 pm
Posts: 3
How to model this? This is a not-infrequent idiom in the object world, and would be trivial to implement if I could write to the database myself....but in Hibernate it proves not-so-easy.

Code:
class Person {
  String name;
  Hand left;
  Hand right;
}

class Hand {
  int numberOfFingers
  int numberOfWarts
  // etc...a non-trivial class
}


Basically, the parent class, Person, has a finite number of children (hands) that have distinct roles (left, right).

Were I to model the database myself, I'd end up with something like:

Code:
table person (
    person_id number,
    name varchar,
    left_hand_id number,
    right_hand_id number
)

table hand (
    hand_id number,
    person_id number,
    numberOfFingers number,
    -- etc...
)


However, Hibernate models this in the more traditional database method of doing things:

Code:
table person (
    person_id number,
    name varchar,
)

table hand (
    hand_id number,
    person_id number,
    numberOfFingers number,
    -- etc...
)


...which results in lots of problems all related to the fact that we've lost the role of our hand.

The only solution I can some up with so far is to use a collection to represent the hands and use some method (order, type attribute) to distinguish between the left and the right. This seems very icky to have the internals of the object modelled to appease the persistence mechanism.

Anyone smarter than me have thoughts?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 20, 2004 2:47 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Two approaches:

1) If the "Hand" class in your example reallys maps to a conceptual entity, then it sounds like you really want to model it as two seperate one-to-many associations from person. In the DB world, that conceptualization would give you exactly the tables as what you listed. The only way hibernate would generate the schema you mention it did is if you tried modeling it as some form of collection association. Check out the <many-to-one/> mapping element of <class/> for info on setting this up.

2) In your example, Hand would really be modelled as a component not an entity, since a hand cannot exist without the person (well at least not in a living-manner). That would give you the following in the db schema:
table person (
person_id number,
name varchar,
left_hand_finger_cnt number,
left_hand_wart_cnt number,
right_hand_finger_cnt number,
right_hand_wart_cnt number
-- etc...
)


Top
 Profile  
 
 Post subject: Thanks, and one last small problem
PostPosted: Mon Feb 23, 2004 7:28 pm 
Newbie

Joined: Fri Feb 20, 2004 2:22 pm
Posts: 3
Steve -

Thanks for the tip. I followed approach one, since I was modelling this with the intent of Hand being a domain object.

I'm left with the following error that I can work around, but would rather not have to:

Code:
net.sf.hibernate.PropertyValueException: not-null property references a null or transient value: eg.Hand.person
at net.sf.hibernate.impl.SessionImpl.checkNullability(SessionImpl.java:1211)
at net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:873)
at net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:817)
at net.sf.hibernate.impl.SessionImpl.saveWithGeneratedIdentifier(SessionImpl.java:740)
at net.sf.hibernate.impl.SessionImpl.save(SessionImpl.java:717)
at net.sf.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:1322)
at net.sf.hibernate.engine.Cascades$4.cascade(Cascades.java:114)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:436)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:503)
at net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:843)
at net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:817)
at net.sf.hibernate.impl.SessionImpl.saveWithGeneratedIdentifier(SessionImpl.java:740)
at net.sf.hibernate.impl.SessionImpl.save(SessionImpl.java:717)


Person.java
Code:
public class Person extends DomainObject {
    private static final Log LOG = LogFactory.getLog(Person.class);

    private Hand leftHand;
    private Hand rightHand;

    public void createLeftHand(int numberOfFingers) {
        leftHand = new Hand(this, numberOfFingers);
    }

    public void createRightHand(int numberOfFingers) {
        rightHand = new Hand(this, numberOfFingers);
    }

    public void removeLeftHand() {
        UnitOfWork.get().removeEntity(leftHand);
        leftHand = null;
    }

}


Hand.java:
Code:
public class Hand extends DomainObject {
    private static final Log LOG = LogFactory.getLog(Hand.class);

    private Person person;
    private int numberOfFingers;

    public Hand(Person person, int numberOfFingers) {
        this.person = person;
        this.numberOfFingers = numberOfFingers;
    }

    private Hand() {}

}


Relevant portion of hibernate mapping:
Code:
    <class name="eg.Person" table="person">
        <id name="id" type="long" column="id">
            <generator class="sequence">
                <param name="sequence">objectid_sequence</param>
            </generator>
        </id>

       <many-to-one name="leftHand" access="field" cascade="all" column="left_hand_id"/>
       <many-to-one name="rightHand" access="field" cascade="all" column="right_hand_id"/>
    </class>

    <class name="eg.Hand" table="hand">
        <id name="id" type="long" column="id">
            <generator class="sequence">
                <param name="sequence">objectid_sequence</param>
            </generator>
        </id>

        <many-to-one name="person" access="field" column="person_id" cascade="save-update" not-null="true" />
        <property name="numberOfFingers" access="field" type="int"/>
    </class>


If I change the many-to-one mapping in Hand to have not-null="false", then the error goes away. Smells to me like the objects are being persisted out of order. The code I'm using calls Session.save() on the person object, and I would assume the cascade setting would take care of everything.

Anyway...thanks for the help, and any insight into the above error would be appreciated.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Feb 24, 2004 8:58 pm 
Newbie

Joined: Tue Feb 24, 2004 8:25 pm
Posts: 1
Quote:
If I change the many-to-one mapping in Hand to have not-null="false", then the error goes away. Smells to me like the objects are being persisted out of order. The code I'm using calls Session.save() on the person object, and I would assume the cascade setting would take care of everything.

Setting not-null="false" doesn't really work, because you'll find that the value in the "person_id" column of the "Hand" table will *always* end up null.

The problem you're seeing is that the two related <many-to-one> elements are effectively 2 Foreign Keys referencing each other. If you put not-null FK constraints on these columns, you could never insert the Person-Hand data you want, because you'd always get a FK violation due to a missing row in the related table (no matter which order you attempted the insert).

For a more exact explanation of the behaviour you're seeing: in order to avoid FK constraints, Hibernate nullifies the "unsaved" Person reference of your Hand object prior to insertion, in this code (SessionImpl.java:874):
Code:
      TypeFactory.deepCopy(values, types, persister.getPropertyUpdateability(), values);
      nullifyTransientReferences(values, types, useIdentityColumn, object);
      checkNullability(values, persister, false);


If Hibernate knows that the Person should be "not-null", then the checkNullability fails. If not, the Hand will be created with a null value for person_id. Neither is really what you want, methinks.

To be honest, I can't see an easy way to do what you want. You're effectively asking Hibernate to maintain a redundant column (the person_id column of the Hand table) in order to preserve uniqueness in the Hand-Person relationship, and provide a bidirectional relationship. You could treat Hand.getPersonId() as a simple property, and make sure it is set in code, but you still won't be able to call Hand.getPerson() on a newly loaded instance.

Maybe someone smarter than me can help out with this :)


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 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.