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.