关系映射之一对一
基于主键的一对一
基于主键的一对一就是说:对于从表,它的主键就是它的外键。那么这个一对一关系就非常明确。
经典案例:人<–>身份证,Person的id同时是IDCard的id
我们建立项目HibernateOneToOne
进行测试。
两个domain对象以及它们的映射文件如下:
Person
对象以及映射文件:
package com.gavin.domain;
import java.io.Serializable;
public class Person implements Serializable{
private int id;
private String name;
private IDCard idCard;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public IDCard getIdCard() {
return idCard;
}
public void setIdCard(IDCard idCard) {
this.idCard = idCard;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.gavin.domain">
<class name="com.gavin.domain.Person" table="person" schema="hibernate">
<id name="id" column="id" type="java.lang.Integer">
<generator class="increment"/>
</id>
<property name="name" column="name" length="64" type="java.lang.String"/>
<!--这里配置Person和IdCard属性是一对一的关系-->
<one-to-one name="idCard"/>
</class>
</hibernate-mapping>
IDCard
对象以及它的映射文件:
package com.gavin.domain;
import java.io.Serializable;
import java.util.Date;
public class IDCard implements Serializable {
private int id;
private Date validateTime;
private Person person;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getValidateTime() {
return validateTime;
}
public void setValidateTime(Date validateTime) {
this.validateTime = validateTime;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.gavin.domain.IDCard" table="idcard" schema="hibernate">
<id name="id" type="java.lang.Integer" column="id">
<!--因为IDCard的主键是引用Person的主键,故这里采用外键策略-->
<generator class="foreign">
<!--这里的值是跟哪一个domain属性一对一-->
<param name="property">person</param>
</generator>
</id>
<property name="validateTime" column="validateTime" type="java.util.Date"/>
<one-to-one name="person" constrained="true"/>
</class>
</hibernate-mapping>
这里需要注意的是,在IDCard的映射文件中,主键ID的生成策略必须采用foreign
。另外,one-to-one必须设置constrained='true'
,对应的数据库表idcard才能生成外键关系,否则是不生成外键的。这个结论从Hibernate生成的SQL语句也可以看出来。
- 测试代码如下:
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Person person = new Person();
person.setName("小明");
IDCard idCard = new IDCard();
idCard.setValidateTime(new Date());
// 表明idCard对象是属于person这个对象的
idCard.setPerson(person);
// 这样是不行的,因为是是IDCard关联Person,所以只能idCard.setPerson(person)
// person.setIdCard(idCard);
// 最好先保存Person,再保存idCard
session.save(person);
session.save(idCard);
transaction.commit();
- Hibernate生成的SQL语句如下:
Hibernate: drop table if exists idcard
Hibernate: drop table if exists person
Hibernate: create table idcard (id integer not null, validateTime datetime, primary key (id)) engine=MyISAM
Hibernate: create table person (id integer not null, name varchar(64), primary key (id)) engine=MyISAM
Hibernate: alter table idcard add constraint FK4opm8ny1jtp2g518o4ktymcne foreign key (id) references person (id)
Hibernate: select max(id) from person
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into idcard (validateTime, id) values (?, ?)
- 数据库表结构如下:
基于外键的一对一
基于外键的一对一,Person的id和IDCard的id不一样,此时IDCard增加一列pid来外键关联Person的id,以此形成一对一的关系。
那么这里就有一个问题,如上图所示,外键怎么确保一对一关系而不重复呢?(如果不加额外的约束,一个Person可以同时被两个IDCard关联)
解决这个问题的方案就是:IDCard中的pid有外键约束,同时也有unique约束,要保证pid在IDCard表中也是唯一的。
在基于外键的一对一关系中,domain对象和基于主键的一对一是一样的,只是从表对应的对象的关系映射文件要变一变。
也就是说,这里的IDCard.hbm.xml文件要稍作修改。
其实,这里的one-to-one关系只是many-to-one的一个特例,因为不加约束的话,一个Person可以对应多个IDCard,所以这里我们给IDCard配上many-to-one关系,但是要加上unique='true'
的约束,这样才能符合我们的要求。
- IDCard.hbm.xml修改如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.gavin.domain.IDCard" table="idcard" schema="hibernate">
<id name="id" type="java.lang.Integer" column="id">
<generator class="assigned"/>
</id>
<property name="validateTime" column="validateTime" type="java.util.Date"/>
<many-to-one name="person" column="pid" unique="true"/>
</class>
</hibernate-mapping>
可以看到,改变的地方有两个,一个是主键ID的生成策略,这里由之前的foreign
改成了assigned
;另外就是这里配置的是many-to-one
,但是添加了unique
约束。
- 测试方法需要手动设置IDCard的id号,如下:
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Person person = new Person();
person.setName("小明");
IDCard idCard = new IDCard();
idCard.setId(101010);
idCard.setValidateTime(new Date());
// 表明idCard对象是属于person这个对象的
idCard.setPerson(person);
// 这样是不行的,因为是是IDCard关联Person,所以只能idCard.setPerson(person)
// person.setIdCard(idCard);
session.save(person);
session.save(idCard);
transaction.commit();
- Hibernate生成的SQL语句为:
Hibernate: drop table if exists idcard
Hibernate: drop table if exists person
Hibernate: create table idcard (id integer not null, validateTime datetime, pid integer, primary key (id)) engine=MyISAM
Hibernate: create table person (id integer not null, name varchar(64), primary key (id)) engine=MyISAM
Hibernate: alter table idcard add constraint UK_p21bt5txra8q5jmfpvq8kv2h unique (pid)
Hibernate: alter table idcard add constraint FKq0b0d1t0ol6vj0bfoam5f44eb foreign key (pid) references person (id)
Hibernate: select max(id) from person
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into idcard (validateTime, pid, id) values (?, ?, ?)
可以看到idcard表相比之前又增加了一列pid,并且添加了外键约束和unique约束。
- 生成的表结构如下: