ORM中,对象和关系数据是业务中的两种表现形式,业务实体是在内存中表现为对象,而在数据库中表现未关系数据.但是内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系.
这是Hibernate学习系列的第二篇blog,准备写写关于Hibernate中反应对象之间的关联关系.
1. 集合 值 映射
在上一篇blog中我创建了book实体类,这一节中,我将创建User实体类,而每个User类可能有多个phone number,这个时候我们就可以单独创建phone number 表,并在User中使用集合映射,实现每个用户有多个phone number.
此时可更改如下:
package entities;
import java.util.Set;
/**
* Created by tbxsx on 17-5-26.
*/
public class User {
private long id;
private String username;
private String email;
private Set<String> phoneSet;
public User() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Set<String> getPhoneSet() {
return phoneSet;
}
public void setPhoneSet(Set<String> phoneSet) {
this.phoneSet = phoneSet;
}
}
在User中添加Set phoneSet 属性,在User.hbm.xml中更改如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- Generated 2013-11-11 23:19:21 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="entities">
<class name="entities.User" table="`user`">
<id name="id" type="long">
<generator class="native"/>
</id>
<property name="email" column="`email`"/>
<property name="username" column="`username`"/>
<!--
Set类型的名字是:phoneSet,与之对应的表格是phone,集合中的数据在获得User的时候,立即加载.
-->
<set name="phoneSet" table="`phone`" lazy="false">
<key column="userid"/><!-- 指定 phone表格中的外键-->
<element column="`phone`" type="string" not-null="true"/><!-- 指定与集合元素对应的字段名字未 phone ,映射类型未string,并且不能为空-->
</set>
</class>
</hibernate-mapping>
测试:
@Test
public void testSet(){
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
User user = new User();
Phone phone = new Phone();
phone.setPhone("15821859797");
phone.setUser(user);
user.setUsername("lls");
user.setEmail("email@qq.com");
Set<String> phoneSet = new HashSet<String>();
phoneSet.add(phone.getPhone());
user.setPhoneSet(phoneSet);
session.save(user);
session.getTransaction().commit();
System.out.println("Test!");
}
结果:
说明:在User实体类中添加SetphoneSet属性,然后在user的hbm.xml文件中添加集合对象配置.
在数据库中数据库表如下:
user表格:
字段名 | 类型 | 说明 |
---|---|---|
id | long | 主键,自增 |
username | varchar | 无 |
varchar | 无 |
Phone表格:
字段名 | 类型 | 说明 |
---|---|---|
userid | long | 外键,用户编号,引用user表格中的id列 |
phone | varchar | 电话号码 |
值得注意的是,这里我们并没有创建Phone.hbm.xml文件,Phone表格是在User.hbm.xml文件set属性创建的.
我们分析一下hibernate怎样根据需要找到phone的.
我们可以把他类比为 到NPC处领任务.那么我们需要知道地点,然后是该地点的哪一个NPC,最后NPC可能有多个任务,我们需要确定接受哪一个任务.
在POJO中集合属性
private Set<String> phoneSet;
中的每个元素是怎样找到的,首先根据set(hbm.xml中)的name属性
<set name="phoneSet" table="`phone`" lazy="false">
<key column="userid"/><!-- 指定 phone表格中的外键-->
<element column="`phone`" type="string" not-null="true"/><!-- 指定与集合元素对应的字段名字未 phone ,映射类型未string,并且不能为空-->
</set>
那么这是与之对应的映射,然后有这几个问题:
1. 去哪张table中寻找?
set中的table属性回答了这个问题
```
table="`phone`"
```
去phone表中找.
这是地点
2. 这张表格中有那么多元组,该找哪一个元组呢?
hibernate中的key属性回答了这个问题:
```
<key column="userid"/>
```
去寻找userid与User表格中id相等(如果制定property-key = XXX,则与
这是地点之下的人物(NPC)
可参考:blog
3. 找到了元组,我该取走那一个元素?
这就用到了最后一个属性
```
<element column="`phone`" type="string" not-null="true"/><!-- 指定与集合元素对应的字段名字未 phone ,映射类型未string,并且不能为空-->
```
取走类型为String的phone.
**这是任务**
至此我们完成了去NPC处取走人物的全部流程,也明确了hibernate查询的方式
集合属性中当然还有List和Map属性,这就相当于从NPC处取走的任务在我们的任务栏中还有一定的顺序.
因此需要加一个属性来确定任务在我们任务栏的位置:
List:
<list name="phoneSet" table="`phone`" lazy="false">
<key column="`userid`"/>
<!--确定在任务栏中的位置-->
<list-index column="idx"/>
<element type="string" column="`phone`" not-null="true"/>
</list>
数据库phone表中会多加上一个属性 idx
,用来确定位置.
Map:
<map name="phoneSet">
<key column="`userid`"/>
<index column="key" type="string"/>
<element column="`phone`" type="string" not-null="true"/>
</map>
数据库表中会多加上一个属性key
varchar类型(通过 添加的)来确定在任务栏中的位置.
2. 实体对象之间的关系映射
接下来,有了上面值集合的经验,我们来接触一下实体对象之间的关系映射.
首先我们讲实体对象之间的关系映射分一下类,了解一下一共有哪些类型的关联关系:
方向 | 关联侧 | 单向一对多 | 单向多对一 | 双向多对一 | 单向多对多 | 双向多对多 |
---|---|---|---|---|---|---|
单 | 一 | set,one-to-many | 无 | set,one-to-many | 无 | 无 |
单 | 一 | 无 | 无 | 无 | 无 | 无 |
双 | 多 | 无 | many-to-one | one-to-many | set,many-to-many | set,many-to-many |
双 | 多 | 无 | 无 | 无 | 无 | set,many-to-many |
注:如果注明单向X对Y,那么X知道Y而Y不知道X.
对与一对一关系,分为两大类:
1.基于主键的一对一关系
也就是说,一个表格的主键也是它的外键.
比如说,我们假设每个Husband 只有一个wife,而每个wife也只有一个Husband
package entities;
/**
* Created by tbxsx on 17-5-28.
*/
public class Husband {
private long id;
private String name;
private Wife wife;
public Husband() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Wife getWife() {
return wife;
}
public void setWife(Wife wife) {
this.wife = wife;
}
}
Wife:
package entities;
/**
* Created by tbxsx on 17-5-28.
*/
public class Wife {
private long id;
private String name;
private Husband husband;
public Wife() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Husband getHusband() {
return husband;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
}
hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- Generated 2013-11-11 23:19:21 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="entities">
<class name="entities.Husband" table="`husband`">
<id name="id" type="long">
<generator class="native"/>
</id>
<property name="name" column="`name`"/>
<one-to-one name="wife" class="entities.Wife"></one-to-one>
</class>
<class name="entities.Wife" table="`wife`">
<id name="id" type="long">
<generator class="foreign">
<param name="property">husband</param>
</generator>
</id>
<property name="name" column="`name`"/>
<one-to-one name="husband" class="entities.Husband" constrained="true"></one-to-one>
</class>
</hibernate-mapping>
注意:保存时,若只保存一个实体类,当保存没有外键的实体类的时候,会保存两个类,否则只会保存一个实体类.
2.基于外键的一对一关系
在多对一关系中,多方加上unique属性,即外键唯一.
双向多对多关系
以User和Address之间的多对多关系为例,一个User可能有多个Address,而一个Address而有可能有多个User,因此在数据库中将会存在一个关系表user_address:
字段名 | 类型 | 说明 |
---|---|---|
aid | long | 外键,依赖于Address的id |
uid | long | 外键,依赖于User的id |
此时User.java类如下:
package entities;
import java.util.Set;
/**
* Created by tbxsx on 17-5-26.
*/
public class User {
private long id;
private String username;
private String email;
private Set<String> phoneSet;
private Set<Address> addressSet;
public User() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Set<String> getPhoneSet() {
return phoneSet;
}
public void setPhoneSet(Set<String> phoneSet) {
this.phoneSet = phoneSet;
}
public Set<Address> getAddressSet() {
return addressSet;
}
public void setAddressSet(Set<Address> addressSet) {
this.addressSet = addressSet;
}
}
可见多出了一个Set< Address >addressSet属性.
在User.hbm.xml中配置如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- Generated 2013-11-11 23:19:21 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="entities">
<class name="entities.User" table="`user`">
<id name="id" type="long">
<generator class="native"/>
</id>
<property name="email" column="`email`"/>
<property name="username" column="`username`"/>
<set name="phoneSet" table="`phone`" lazy="false">
<key column="userid" foreign-key=""/>
<element column="`phone`" type="string" not-null="true"/>
</set>
<!-- -->
<!-- name="addressSet" 告诉我们配置的是addressSet映射关系,数据库中表是user_address -->
<set name="addressSet" table="`user_address`" cascade="save-update">
<!-- 在user_address table中添加外键uid,默认依赖于user table中的主键 -->
<key column="uid"></key>
<!--
由于是多对多关系,在找到user_address中uid与user table中id相等的元组时,元组会有多个,
因此通过另一个外键 aid(user_address table中)到Address中去找id与aid相等的,
即可找到相应的Address
-->
<many-to-many column="aid" class="entities.Address"/>
</set>
</class>
</hibernate-mapping>
同理,在Address.hbm.xml配置如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- Generated 2013-11-11 23:19:21 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="entities">
<class name="entities.Address" table="`address`">
<id name="id" type="long">
<generator class="native"/>
</id>
<property name="name" column="`name`"/>
<set name="userSet" table="`user_address`" cascade="save-update">
<key column="aid"></key>
<many-to-many column="uid" class="entities.User"/>
</set>
</class>
</hibernate-mapping>
由此就建立了双向多对多关系.
总结:
1. 1-n关系或者n-1关系:
数据库中在多的一方映射的表格中加入外键,所不同的是,在XXX.hbm.xml文件中,若是一方知道多方,那么是在一方加入
<set name="userSet" table="one_many">
<key column="many_id"></key>
<one-to-many class="many_class"></one-to-many>
</set>
也就是说,外键是通过在一方加入的.
若是多方知道一方,那么在多方加入:
<many-to-one name="one_class"class="one_class"column="table_fk"></many-to-one>
双向多对一:
上面两个都要配置.
一对一关系:
分为基于外键的和基于主键的.
前者使用one-to-one,如上演示的Husband和Wife关系.
后者使用many-to-one和one-to-one关系,其中many-to-one侧加入unique属性.
多对多关系:
都是配置many-to-many,set.