Hibernate 一对一关联映射
1. 前言
本节课程和大家一起聊聊关联映射。通过本节课程的学习,你将了解到:
- 什么是关联映射;
- 如何实现一对一关联映射。
2. 关联映射
关系数据库中的数据以表为家,一张表一个家,一个家住一类数据。众多表组成关系型社区,关系型社区群体中的数据关系通过主外键方式描述。
表与表之间的数据根据彼此的关系可分为:
- 一对一关系: 如老公表和老婆表的关系;
- 一对多关系: 如用户表和银行账号表关系;
- 多对一关系: 如银行帐号表对用户表关系;
- 多对多关系: 如学生表和课程表关系。
不管是哪种关系,都可以通过主外键方式联系。
一对多、多对一本质一样,正如一块硬币的正反面,看待同一个事物的角度不同。
多对多通过中间表的方式拆分成两个一对多(多对一)。
以上都是关系型数据库中的基础知识,美好的回忆有助于巩固。
开发者使用 Hibernate 操作某一张表中的数据时,有 2 件事情要做:
- 在程序中构建一个与表结构相似的类(这个类可称为实体 [entity] 类);
- 使用注解或 XML 语法把类结构和表结构关联映射起来(此时这个类可称为 PO)。
有了 PO,Hibernate 就能在程序和数据库之间进行数据贸易往来。
2.1 新的需求总是接踵而至
如果程序要求 Hibernate 操作多张表中的数据,并要求把获取的数据封装到对象中,又如何操作?
程序说:亲爱的 Hibernate,帮我查询一下学生信息和学生的家庭地址信息。
差点忘记告诉你:学生信息和学生的家庭地址信息分别存放在两张表中,且一名学生只能有一个地址。
这是假设,所以请不要纠结这种假设。
Hibernate 说: 可以。但有几件事情必须按要求做好,否则干不了。
- 首先须确定数据库中存在学生表和地址表,并确定两者关系。使用地址编号 addressId 字段作共同字段,addressId 在地址表中是主键,在学生表中是外键。
- 回到程序,构建两个类:Student 类、Address 类,添加注解描述。分别映射学生表、地址表。
Hibernate 说:还不够。
数据库中的表之间是有关系的,这种关系在 Student 类 和 Address 类 中也必须体现出来。
这也是 Hibernate 能查询到多表中数据的核心要求。
2.2 PO 之间映射表之间的关系
从编码层面上讲,就是如何在 Student 类 与 Address 类 之间体现出数据库表中数据之间的关系。
先从 Student 类 中开始,在 Student 类 中添加一个属性字段。
private Address address;
address 属性是一个 Address 类类型,数据库不认得这玩意儿,Hibernate 表示开始要一个头两个大了,眩晕啦。
null 你先进入学生表,然后根据学生表中的 addressId 进入到地址表,找到对应数据。
Hibernate 又没有读心术,它如何知道你心里所想。
所以,需要通过 XML 或注解语法把开发者的想法告诉 Hibernate:
private Address address;
@OneToOne(targetEntity = Address.class)
@JoinColumn(name = "addressId")
public Address getAddress() {
return address;
}
@OneToOne 注解告诉 Hibernate :address 属性的值要麻烦您先找到 学生表的 addressId,再辛苦去一下 地址表,把对应的地址信息查询出来;
@JoinColumn:告诉 Hibernate 带着 addressId 到地址表中查找。
主配置文件中添加如下信息:
<property name=*"hbm2ddl.auto"*>create</property>
<mapping class=*"com.mk.po.Student"* />
<mapping class=*"com.mk.po.Address"* />
在 Hibernate 创建完毕后,添加几条测试数据,操作完成后别忘记改回来。
此处操作自动完成!相信聪明如你,一定没问题。
<property name="hbm2ddl.auto">update</property>
2.3 测试时间
查询学生及学生的地址信息:
try {
transaction = session.beginTransaction();
Student stu = (Student) session.get(Student.class, new Integer(1));
System.out.println("----------------学生信息---------------");
System.out.println("学生姓名:" + stu.getStuName());
System.out.println("-----------------地址信息-----------------");
System.out.println("学生家庭地址:"+stu.getAddress().getAddressName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
输出结果:
Hibernate:
select
student0_.stuId as stuId1_1_1_,
student0_.addressId as addressI6_1_1_,
student0_.stuName as stuName2_1_1_,
student0_.stuPassword as stuPassw3_1_1_,
student0_.stuPic as stuPic4_1_1_,
student0_.stuSex as stuSex5_1_1_,
address1_.addressId as addressI1_0_0_,
address1_.addressName as addressN2_0_0_,
address1_.descript as descript3_0_0_
from
Student student0_
left outer join
Address address1_
on student0_.addressId=address1_.addressId
where
student0_.stuId=?
----------------学生信息---------------
学生姓名:Hibernate老大
-----------------地址信息-----------------
学生家庭地址:北京
Hibernate 使用 left outer join 构 建一条 Sql 语句,一次性访问 2 张表,同时获取了学生信息和地址信息;
请问 Hibernate,你能不能查询地址信息时,查询出地址是哪个学生的。
可以,但是,需要在地址类中添加如下代码:
private Student student;
@OneToOne(targetEntity=Student.class,mappedBy="address")
public Student getStudent() {
return student;
}
@OneToOne 注解的 targetEntity=Student.class 告诉 Hibernate,此属性的值对应的是学生表中的数据;
在 Student 类已经使用了 @OneToOne 映射。mappedBy=“address” 意思是说:Hibernate!student 类中已经说明的够清楚了吧,这里就不要我再啰嗦了。
测试实例:
try {
transaction = session.beginTransaction();
Address address = (Address) session.get(Address.class, new Integer(1));
System.out.println("----------------地址信息---------------");
System.out.println("地址信息:" + address.getAddressName());
System.out.println("-----------------学生信息-----------------");
System.out.println("学生姓名:" + address.getStudent().getStuName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
输出结果:Hibernate 自动构建了多表查询语句,一次性从数据库获取所有数据。
Hibernate:
select
address0_.addressId as addressI1_0_1_,
address0_.addressName as addressN2_0_1_,
address0_.descript as descript3_0_1_,
student1_.stuId as stuId1_1_0_,
student1_.addressId as addressI6_1_0_,
student1_.stuName as stuName2_1_0_,
student1_.stuPassword as stuPassw3_1_0_,
student1_.stuPic as stuPic4_1_0_,
student1_.stuSex as stuSex5_1_0_
from
Address address0_
left outer join
Student student1_
on address0_.addressId=student1_.addressId
where
address0_.addressId=?
----------------地址信息---------------
地址信息:北京
-----------------学生信息-----------------
学生姓名:Hibernate老大
无论是从学生表查询到地址表,还是从地址表查询到学生表。只要有足够的信息告诉 Hibernate 如何关联到数据库中对应的表,Hibernate 都会如你所愿。
3. 关联映射中的延迟加载
关联多表查询时可选择是否启用延迟加载。
PO 之间的映射,意味着 Hibernate 不仅能查询到指定表中数据,还能查询相关联表中的数据。
但,有时只需要查询学生基本信息,并不需要地址信息,或者地址信息并不需要马上查询出来,能不能告诉 Hibernate,只查询学生信息,暂且别查询地址信息。
同样,有时只需要查询所在地址,并不关心地址对应学生信息。
可以启动关联映射中的延迟加载实现上面的需求。
学生类中修改代码如下:
@OneToOne(targetEntity = Address.class,fetch=FetchType.LAZY)
@JoinColumn(name = "addressId")
public Address getAddress() {
return address;
}
@OneToOne 注解有 fetch 属性,为枚举类型,其值可选择:
- FetchType.LAZY;
- FetchType.EAGER。
其作用便是告诉 Hibernate,是否延后或立即查询相关联表中的数据。
执行下面测试代码:
try {
transaction = session.beginTransaction();
Student stu = (Student) session.get(Student.class, new Integer(1));
System.out.println("----------------学生信息---------------");
System.out.println("学生姓名:" + stu.getStuName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
查看结果:
Hibernate:
select
student0_.stuId as stuId1_1_0_,
student0_.addressId as addressI6_1_0_,
student0_.stuName as stuName2_1_0_,
student0_.stuPassword as stuPassw3_1_0_,
student0_.stuPic as stuPic4_1_0_,
student0_.stuSex as stuSex5_1_0_
from
Student student0_
where
student0_.stuId=?
----------------学生信息---------------
学生姓名:Hibernate老大
Hibernate 只构建了一条简单的 Sql 语句, 用于查询学生信息。
继续执行下面测试实例:
try {
transaction = session.beginTransaction();
Student stu = (Student) session.get(Student.class, new Integer(1));
System.out.println("----------------学生信息---------------");
System.out.println("学生姓名:" + stu.getStuName());
System.out.println("-----------------地址信息-----------------");
System.out.println("学生家庭地址:" + stu.getAddress().getAddressName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
输出结果:
Hibernate:
select
student0_.stuId as stuId1_1_0_,
student0_.addressId as addressI6_1_0_,
student0_.stuName as stuName2_1_0_,
student0_.stuPassword as stuPassw3_1_0_,
student0_.stuPic as stuPic4_1_0_,
student0_.stuSex as stuSex5_1_0_
from
Student student0_
where
student0_.stuId=?
----------------学生信息---------------
学生姓名:Hibernate老大
-----------------地址信息-----------------
Hibernate:
select
address0_.addressId as addressI1_0_0_,
address0_.addressName as addressN2_0_0_,
address0_.descript as descript3_0_0_
from
Address address0_
where
address0_.addressId=?
学生家庭地址:北京
Hibernate 分别构建了 2 条简单的查询 Sql 语句,可得出结论:
- 只有当需要获取地址信息时,才会构建 Sql 语句查询地址表;
这就是关联映射中的延迟加载。
- @OneToOne 默认情况下是采用立即策略,通过构建多表查询语句一次性全部查询。
4. 小结
本节课讲解了如何通过实现实体类之间的映射,以保证 Hibernate 能正常访问开发者所需要的关联表中的数据。其中有些细节暂未深究。
当介绍一对多、多对多映射关系时,再抽丝剥茧般展开。
OK!意犹未尽之处,下一节课程再畅聊。