-->
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.  [ 10 posts ] 
Author Message
 Post subject: one-to-one lazy initialization example using primary keys
PostPosted: Thu Oct 21, 2004 11:08 am 
Beginner
Beginner

Joined: Tue Oct 19, 2004 11:04 am
Posts: 22
I have noticed that there have been several unsatisfied requests for an example of one-to-one relationships that can be lazily loaded. After much trial and error, I have come up with an example that I can share. One caveat: I was not able to get my example to work with anything other than a customized primary key generator (or using the 'assigned' generator). After thinking about the order in which Hibernate must do things, this makes sense, although it probably puts a damper on things for a lot of people. Anyway, here is my example.

Hibernate version: 2.1.2
Database: Sybase (but this shouldn't be important)

I have three classes: A Father class that has one-to-one relationships with both a Son class and a Daughter class,
Code:
public class Father {

    private String id;
    private String name;
    private Son son;
    private Daughter daughter;

    public Father() {
        super();
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public Son getSon() {
        return son;
    }
    public void setSon(Son child) {
        this.son = child;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Daughter getDaughter() {
        return daughter;
    }
    public void setDaughter(Daughter daughter) {
        this.daughter = daughter;
    }
}

public class Daughter {

    private String id;
    private String name;
    private Father father;
   
    public Daughter() {
        super();
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Father getFather() {
        return father;
    }
    public void setFather(Father parent) {
        this.father = parent;
    }
}

public class Son {

    private String id;
    private String name;
    private Father father;
   
    public Son() {
        super();
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Father getFather() {
        return father;
    }
    public void setFather(Father parent) {
        this.father = parent;
    }
}


I used the primary key one-to-one association pattern. Here are my tables:
Code:
create table father (
   id CHAR(36) not null,
   name VARCHAR(30) not null,   
   primary key (id)
)

create table son (
   id CHAR(36) not null,
   name VARCHAR(30) not null,
   primary key (id)
)

create table daughter (
   id CHAR(36) not null,
   name VARCHAR(30) not null,
   primary key (id)
)


Please note the lack of foreign keys between son and father and daughter and father. From a strictly database-driven standpoint, it would be a good thing to have the id columns of daughter and son have a foreign key relationship to father....however, in order to do lazy initialization, Hibernate requires that the son and daughter records exist before the father record. More on this below...

Here are the mappings (extraneous info excluded):
Code:
    <class name="Father" table="father">
        <id name="id" type="string">
            <column name="id" />
            <generator class="onetoone.HibernateUUIDGenerator"/>
        </id>
       
        <property name="name" type="string">
            <column name="name" />
        </property>
       
        <one-to-one name="son" class="Son" constrained="true" outer-join="false" cascade="all"/>
       
        <one-to-one name="daughter" class="Daughter" constrained="true" outer-join="false" cascade="all"/>
       
    </class>

    <class name="Son" proxy="Son" table="son">
        <id name="id"  type="string">
            <column name="id"/>
            <generator class="foreign">
                <param name="property">father</param>
            </generator>
        </id>
        <property name="name" type="string">
            <column name="name" />
        </property>
       
        <one-to-one name="father" class="Father" cascade="none"/>               

    </class>

    <class name="Daughter" proxy="Daughter" table="daughter">
        <id name="id"  type="string">
            <column name="id"/>
            <generator class="foreign">
                <param name="property">father</param>
            </generator>
        </id>
        <property name="name" type="string">
            <column name="name" />
        </property>
       
        <one-to-one name="father" class="Father" cascade="none"/>               

    </class>


Note that the Father class uses a custom id generator and that the son and daughter class use the 'foreign' id generator that references the father. You could also generate the father's id using the 'assigned' generator and manually assign the id to the father instance in your java code prior to saving.

Note also that the father's one-to-one tags both contain the constrained=true and outer-join=false attributes. The son and daughter class tags both contain the proxy attribute. With these things combined, the lazy initialization is possible.

Here is the code that I used to insert and then select the Father instance:
Code:
public class Tester {

    private SessionFactory sessionFactory;
   
    public static void main(String[] args) {
        new Tester();
    }
   
    public Tester() {
        try {
            Configuration cfg = new Configuration();
            sessionFactory = cfg.configure().buildSessionFactory();
           
            load(insert());
        }
        catch (HibernateException he) {
            he.printStackTrace();
        }
    }
   
    private String insert() throws HibernateException {
        Father f = new Father();
        f.setName("Father");
       
        Son c = new Son();
        c.setName("Son");
       
        f.setSon(c);
        c.setFather(f);
       
        Daughter d = new Daughter();
        d.setName("Daughter");
       
        f.setDaughter(d);
        d.setFather(f);
       
        Session session = sessionFactory.openSession();
        Transaction trans = session.beginTransaction();
       
        session.save(f);
       
        trans.commit();
        session.close();   

        return f.getId();
    }
   
    private void load(String pk) throws HibernateException {
       
        Session session = sessionFactory.openSession();
        Transaction trans = session.beginTransaction();
       
        Father f = (Father)session.load(Father.class, pk);
       
        trans.commit();
        session.close();
    }
}


Note that I use two completely different Session instances to perform first the insert (save) and then the select (load).

More explanation: Since Hibernate requires that the child instances exist in the database before the parent instances when inserting, the following is what happens when an insert occurs:
  • Hibernate uses the custom id generator of the father to create the father record's id...but does not yet insert the father record.
  • Hibernate assigns the father's id to the son and daughter instances.
  • Hibernate inserts the son and daughter records. The order here does not matter.
  • Hibernate inserts the father record.

The end result is that all three records in the three tables have the same id value as their primary key.

Finally, when Hibernate does a load() on the parent, the son and daughter records are NOT returned immediately (i.e. they can be loaded lazily).

Here is the SQL output from Hibernate (run via an Ant script):

Code:
test:
     [java] 10:31:35,503 DEBUG SQL:237 - insert into son (name, id) values (?, ?)
     [java] 10:31:35,513 DEBUG SQL:237 - insert into daughter (name, id) values (?, ?)
     [java] 10:31:35,523 DEBUG SQL:237 - insert into father (name, id) values (?, ?)
     [java] 10:31:35,543 DEBUG SQL:237 - select father0_.id as id0_, father0_.name as name0_ from father father0_ where father0_.id=?


Notice the insert into son first, then daughter, then father. The final select only returns the father record.

Hope this helps! I am going to try the same with foreign keys. I'll post if I succeed.


Top
 Profile  
 
 Post subject: Re: one-to-one lazy initialization example using primary key
PostPosted: Thu Oct 21, 2004 11:27 am 
Beginner
Beginner

Joined: Tue Oct 19, 2004 11:04 am
Posts: 22
jwisard wrote:

Here is the SQL output from Hibernate (run via an Ant script):

Code:
test:
     [java] 10:31:35,503 DEBUG SQL:237 - insert into son (name, id) values (?, ?)
     [java] 10:31:35,513 DEBUG SQL:237 - insert into daughter (name, id) values (?, ?)
     [java] 10:31:35,523 DEBUG SQL:237 - insert into father (name, id) values (?, ?)
     [java] 10:31:35,543 DEBUG SQL:237 - select father0_.id as id0_, father0_.name as name0_ from father father0_ where father0_.id=?



To be more explicit, here is the same SQL output but with binding parameters included. This shows that the same UUID is used for the primary keys for all three tables.

Code:
test:
     [java] 11:23:42,832 DEBUG SQL:237 - insert into son (name, id) values (?, ?)
     [java] 11:23:42,832 DEBUG StringType:46 - binding 'Son' to parameter: 1
     [java] 11:23:42,842 DEBUG StringType:46 - binding 'BC160F2A-86F3-D11F-115F-C40C2FD3DBEC' to parameter: 2
     [java] 11:23:42,852 DEBUG SQL:237 - insert into daughter (name, id) values (?, ?)
     [java] 11:23:42,852 DEBUG StringType:46 - binding 'Daughter' to parameter: 1
     [java] 11:23:42,852 DEBUG StringType:46 - binding 'BC160F2A-86F3-D11F-115F-C40C2FD3DBEC' to parameter: 2
     [java] 11:23:42,862 DEBUG SQL:237 - insert into father (name, id) values (?, ?)
     [java] 11:23:42,862 DEBUG StringType:46 - binding 'Father' to parameter: 1
     [java] 11:23:42,862 DEBUG StringType:46 - binding 'BC160F2A-86F3-D11F-115F-C40C2FD3DBEC' to parameter: 2
     [java] 11:23:42,902 DEBUG SQL:237 - select father0_.id as id0_, father0_.name as name0_ from father father0_ where father0_.id=?
     [java] 11:23:42,902 DEBUG StringType:46 - binding 'BC160F2A-86F3-D11F-115F-C40C2FD3DBEC' to parameter: 1
     [java] 11:23:42,912 DEBUG StringType:68 - returning 'Father' as column: name0_


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 21, 2004 3:52 pm 
Newbie

Joined: Mon Aug 30, 2004 1:37 am
Posts: 16
Hi jwisard,
First off thanks for posting this code.
I have been trying to find a solution for lazily loading an optional one-to-one relationship and have not come up with a satisfactory one. Your solution works well for Fathers who always have one daughter and one son. Have you looked at this case - e.g. a Father who has one son and no daughter? Since constrained="true" on Father to both Daughter and Son in your mappings they must both exist or an exception is thrown:
Code:
     [java] Exception in thread "main" net.sf.hibernate.PropertyValueException: not-null property references a null or transient value: Father.daughter

test code:
Code:
   
private String insertFatherWithNoDaughter() throws HibernateException {
        Father f = new Father();
        f.setName("FatherNoDaughter");
       
        Son c = new Son();
        c.setName("SonNoSister");
       
        f.setSon(c);
        c.setFather(f);
       
        Session session = sessionFactory.openSession();
        Transaction trans = session.beginTransaction();
       
        session.save(f);
       
        trans.commit();
        session.close();   

        return f.getId();
    }   


I have tried some pretty ugly things to get this optional one-to-one lazy loading to work and did kinda get one solution to work - but it was nasty and confused Hibernate when attempting to access a non-existant child from the parent. Since you are trying out things in the one-to-one mapping area, I thought I'd mention it to pique your curiosity and maybe you will have better luck than I.
Cheers,
Grainne.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 21, 2004 4:08 pm 
Beginner
Beginner

Joined: Tue Oct 19, 2004 11:04 am
Posts: 22
Well, what you want to do would be very nice. However, Hibernate does not support it.

Take a look at this for a (somewhat complete) explanation:

http://www.hibernate.org/162.html


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 21, 2004 5:24 pm 
Newbie

Joined: Mon Aug 30, 2004 1:37 am
Posts: 16
Hi jwisard,

yes, I think I have read every wiki, forum posting etc on one-to-one associations and know that by using a normal one-to-one mapping it cannot be done. I did post a question regarding this ( http://forum.hibernate.org/viewtopic.php?t=935504 ) but nobody took it up - I should have mentioned that.

I just wonder how people handle this situation - do they resort to using 1:n/n:1 mappings (with corresponding changes in their domain model classes) to enable lazy loading of optional one to one relationships in the database - so they avoid the performance hit - or use straight jdbc when accessing the table? My kludge involved using a 1:1 mapping on the child and a n:1 mapping on the parent - but as I said it was not a satisfactory solution as it understandably confused Hibernate in certain situations.

Sorry, I don't mean to hijack your thread - so I'll cut out now.
Thanks again.
Grainne.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Oct 22, 2004 7:25 am 
Beginner
Beginner

Joined: Tue Oct 19, 2004 11:04 am
Posts: 22
Don't be sorry! I think you've got some good questions about some things that frustrate an aweful lot of people, including myself.

I'm sure there is a good technical reason for requiring that one-to-one relationships are constrained. However, in my view, Hibernate is just a tool that will allow us to abstract away JDBC from our code, which lets us think more on the domain and not on SQL. But it should be able to handle just about anything that we could do using SQL directly. It would be fairly straightforward to write SQL (or not write SQL) that could pull only a Father instance and a Daughter instance without the pulling the Son. So it doesn't seem to be too much to ask to have Hibernate provide a way to do that too...either through the Query or Criteria API or through the mapping documents and using the load() method. I mean, I can write a SQL statement that joins just on Father and Daughter and then manually build the domain objects from the results without concerning myself with Son. Why can't Hibernate do that too?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 22, 2004 2:05 am 
Newbie

Joined: Wed Dec 08, 2004 8:56 am
Posts: 12
Location: China ChengDu
Hi, jwisard

Note that the Father id generator:
Quote:
<id name="id" type="string">
<column name="id" />
<generator class="onetoone.HibernateUUIDGenerator"/>
</id>


The generator class is "onetoone.HibernateUUIDGenerator". Well, that's ok. But way could not use "native" ? When i defined class="native", The follow SQL:
Code:
Hibernate: insert into father (NAME, ID) values (?, ?)
Hibernate: update son set Name = ? where Id = ?
Hibernate: update daughter set Name = ? where Id = ?


Thanks for your attention!


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 22, 2004 2:07 am 
Newbie

Joined: Wed Dec 08, 2004 8:56 am
Posts: 12
Location: China ChengDu
Sorry, The "way" should be "why".


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 22, 2004 8:32 am 
Beginner
Beginner

Joined: Tue Oct 19, 2004 11:04 am
Posts: 22
Well, it looks like your code is performing updates for son and daughter, not inserts. That implies that you already have instances of son and daughter in the database. I'm not sure how that could be if you're following along with my example. Why would Hibernate perform inserts on records that don't exist yet? How do the son and daughter ID columns get set?

I'm afraid you'll have to post more detail because what little you have given does not make sense. :)


Top
 Profile  
 
 Post subject: Mapping a "one-to-zero-or-one" relationship
PostPosted: Tue Oct 18, 2005 4:46 pm 
Newbie

Joined: Wed Oct 06, 2004 10:34 am
Posts: 16
Location: Teresina - PI - Brasil
Mapping a "one-to-zero-or-one" relationship

I've been searching a way to do a lazy a "one-to-zero-or-one" relationship in Hibernate and I finally found a way to do it using many-to-one with unique="true". See example below:

Association: Person [1 <--> 0..1] Note
Code:
<class name="Person" table="person">
   <id name="id" column="id" type="int" unsaved-value="-1">
       <generator class="sequence">
           <param name="sequence"father_id_seq</param>
       </generator>
   </id>
   <property name="name" type="string"/>
   <many-to-one name="note" class="Note" column="id"
                unique="true" insert="false" update="false"
                cascade="all"/>
</class>

<class name="Note" table="note">
   <id name="id" column="id" type="int" unsaved-value="-1" >
       <generator class="foreign">
           <param name="property">owner</param>
       </generator>
   </id>
   <property name="note" type="string"/>
   <one-to-one name="owner" class="Person" constrained="true"/>
</class>



Observe that column "id" is used twice in Person mapping.

_________________
Regis Pires


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