Hibernate的目的就是让开发人员觉得好像是将JAVA对象存储到了数据库里一样。但是,我们知道数据库中只有表,如何将java对象经过Hibernate存储到数据库中,又如何将数据库中存储的数据通过Hibernate再转换成java对象。这个问题是本文及以后几篇文章要探讨的主要内容。
为什么Hibernate不能自动化地完成映射?
你家Hibernate那么强大,为什么不能自动化地帮我做java对象和数据库表之间的映射呢?
看一个例子:
public class User{
int id;
int userReference;
String name;
String pwd;
Email email;
}
public class Email{
String EmailAddress;
}
如果让你帮Hibernate写自动化映射功能时,你可能会遇到以下问题:
- 是否允许保存没有提供姓名的用户?
- 用户的唯一性是根据id还是根据引用?
- 多个用户可以使用同一个email吗?
这些问题有很多,相同点就是这些问题并没有唯一正确的答案,不同的业务场景有不同的要求。那么,你的自动化映射功能就没有存在的必要了吧?
所以,Hibernate需要开发人员告诉它,Java对象该如何与数据库表之间建立关系。
把上面使用的User类改一下:
public class User{
int id;
int userReference;
String name;
String pwd;
Set<Email> email;
}
public class Email{
String EmailAddress;
}
又出现了两个问题:
- Question 1:一个邮件可以属于多个用户吗?
- Question 2:一个用户可以拥有多个邮件吗?
对这两个问题的回答不同,就决定了User和Email之间不同的关系。
- User和Email 一对一关联:一个邮件只能属于一个用户、一个用户只能拥有一个邮件
- User和Email 一对多关联:一个邮件能属于多个用户、一个用户只能拥有一个邮件
- User和Email 多对一关联:一个邮件只能属于一个用户、一个用户可以拥有多个邮件
- User和Email 多对多关联:一个邮件可以属于多个用户、一个用户可以拥有多个邮件
根据业务场景不同,对象和对象之间有这四种关系,告诉Hibernate你的Java对象和数据库之间的关系就是建立映射。
一对一关联
一对一关联表示两个Java对象之间的一对一关系。在数据库里实现有两种方式:
- 一种方式是给两个类对应的两张数据库表分配相同的主键值,这叫做主键关联;
- 另一种方式是使用一个外键约束从一个表引用另一个表的唯一标识符,这叫做唯一外键关联。
主键关联
还使用User和Email的例子,设定User和Email一对一关联。
public class User{
private int id;
private String name;
private String pwd;
private Email email;
//省略构造函数和getter/setter方法
}
public class Email{
private int id;
private String emailAddress;
//省略构造函数和getter/setter方法
}
使用主键关联的方式会根据主键形成两个表之间一对一的关系:
每条记录都根据主键id形成一对一的关系。
接下来看如何写配置文件。
User.hbm.xml文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--生成默认为user的数据库表-->
<class name="com.zcx.entity.User" table="tbl_user">
<id name="id">
<!-- 采用foreign生成策略,forgeign会取得另外一个关联对象的标识 -->
<generator class="foreign">
<!-- email是User类中关联的对象 -->
<param name="property">email</param>
</generator>
</id>
<property name="name"></property>
<property name="pwd"></property>
<one-to-one name="email" class="com.zcx.entity.Email" constrained="true"/>
</class>
</hibernate-mapping>
一对一关联映射中one-to-one标签指示hibernate如何加载其关联对象。默认根据主键加载,也就是拿到关系字段值,根据对端的主键来加载关联对象。constrained=”true”,表示当前类的主键还是一个外键。参照了对端的主键,也就是会生成外键约束语句。
也就是把Email中的主键拿到User里当主键,从User中能看到Email。
Email.hbm.xml采用的配置文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.zcx.entity.Email" table="tbl_email">
<id name="id">
<generator class="identity"></generator>
</id>
<property name="emailAddress"></property>
</class>
</hibernate-mapping>
根据这两个配置文件,生成的DDL代码是:
create table tbl_email (id integer not null auto_increment, emailAddress varchar(255), primary key (id))
create table tbl_user (id integer not null, name varchar(255), pwd varchar(255), primary key (id))
alter table tbl_user add index FKFE80BACD60578B8 (id), add constraint FKFE80BACD60578B8 foreign key (id) references tbl_email (id)
可以看出,两个表之间形成外键约束语句,这是由于one-to-one标签里设置了constrained=”true”。
唯一外键关联
两张表之间用一个外键来建立一对一关联,其实就是多对一关联的一种特殊例子,也就是说给多对一关联的“多”端加上唯一的限制。
在数据库中的具体体现就是在user对应的表中有了一个email字段它以unique的方式指向了email表。
现在看一下配置文件的写法。
User.hbm.xml文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--生成默认为user的数据库表-->
<class name="com.zcx.entity.User" table="tbl_user">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<property name="pwd"></property>
<many-to-one name="email" class="com.zcx.entity.Email" unique="true"/>
</class>
</hibernate-mapping>
在user端加了一个many-to-one标签,本来是这个是多对一的标签,但是这里可以加uinque=”true”,是一种特殊形式,形成了user和email的一对一关系。
Email.hbm.xml文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.zcx.entity.Email" table="tbl_email">
<id name="id">
<generator class="identity"></generator>
</id>
<property name="emailAddress"></property>
</class>
</hibernate-mapping>
然后再看一下测试代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
SessionFactory factory = new Configuration().configure().buildSessionFactory();
Session session = factory.openSession();
session.beginTransaction();
User user = new User();
Email email = new Email();
user.setName("simon");
user.setPwd("123456");
user.setEmail(email);
email.setEmailAddress("abc@test.com");
session.save(email);
session.save(user);
User user2 = new User();
Email email2 = new Email();
user2.setName("simon2");
user2.setPwd("1234562");
user2.setEmail(email2);
email2.setEmailAddress("abc2@test.com");
session.save(email2);
session.save(user2);
session.getTransaction().commit();
session.close();
}
可以看到运行过测试代码之后数据库的内容:
数据库里是通过tbl_user表里面通过email字段关联到tbl_email表。
另外一点需要说明的是,唯一外键关联方式,必须
session.save(email);
session.save(user);
两个对象都要执行save方法,之前主键关联时,只需要执行save(user)就可以把email连带着持久化进去。