Hibernae 的延迟加载是一个很是经常运用的手艺,实体的调集属性默许会被延迟加载,实体所联络关系的实体默许也会被延迟加载。Hibernate 经由过程这类延迟加载来降落系统的内存开消,从而包管 Hibernate 的运转机能。
上面先来合成 Hibernate 延迟加载的“奥妙”。
调集属性的延迟加载
当 Hibernate 从数据库中初始化某个耐久化实体时,该实体的调集属性是不是随耐久化类一路初始化呢?若是调集属性里包括十万,乃至百万的记实,在初始化耐久化实体的同时,完成一切调集属性的抓取,将致使机能急剧降落。完整有可以系统只需求应用耐久化类调集属性中的部门记实,而完整不是调集属性的全数,多么,没有需求一次加载一切的调集属性。
对调集属性,但凡保举应用延迟加载战略。所谓延迟加载就是等系统需求应用调集属性时才从数据库装载联络关系的数据。
例以下面 Person 类持有一个调集属性,该调集属性里的元素的类型为 Address,该 Person 类的代码片断以下:
清单 1. Person.java
public class Person
{
// 标识属性
private Integer id;
// Person 的 name 属性
private String name;
// 保管 Person 的 age 属性
private int age;
// 应用 Set 来保存调集属性
private Set Address addresses = new HashSet Address
// 上面省略了各属性的 setter 和 getter 方式
...
}
为了让 Hibernate 能办理该耐久化类的调集属性,法式为该耐久化类供应以下映照文件:
清单 2. Person.hbm.xml
?xml version= 1.0 encoding= GBK ?
!DOCTYPE hibernate-mapping PUBLIC
-//Hibernate/Hibernate Mapping DTD 3.0//EN
//www.hibernate.org/dtd/hibernate-mapping-3.0.dtd
hibernate-mapping package= org.crazyit.app.domain
!-- 映照 Person 耐久化类 --
class name= Person table= person_inf
!-- 映照标识属性 id --
id name= id column= person_id
!-- 界说主键天生器战略 --
generator >
/id
!-- 用于映照浅显属性 --
property name= name type= string /
property name= age type= int /
!-- 映照调集属性 --
set name= addresses table= person_address lazy= true
!-- 指定联络关系的外键列 --
key column= person_id /
composite-element >
!-- 映照浅显属性 detail --
property name= detail /
!-- 映照浅显属性 zip --
property name= zip /
/composite-element
/set
/class
/hibernate-mapping
从下面映照文件的代码能够看出,Person 的调集属性中的 Address 类只是一个浅显的 POJO。该 Address 类里包括 detail、zip 两个属性。由于 Address 类代码很是俭朴,故此处不再给出该类的代码。
下面映照文件中 set.../ 元素里的代码指定了 lazy= true (对 set.../ 元夙来说,lazy= true 是默许值),它指定 Hibernate 会延迟加载调集属性里 Address 对象。
例如经由过程以下代码来加载 ID 为 1 的 Person 实体:
Session session = sf.getCurrentSession();
Transaction tx = session.beginTransaction();
Person p = (Person) session.get(Person.class, 1); // 1
System.out.println(p.getName());
下面代码只是需求拜候 ID 为 1 的 Person 实体,其实不想拜候这个 Person 实体所联络关系的 Address 对象。此时有两种环境:
若是不延迟加载,Hibernate 就会在加载 Person 实体对应的数据记实时当即抓取它联络关系的 Address 对象。
若是采取延迟加载,Hibernate 就只加载 Person 实体对应的数据记实。
很较着,第二种做法既能削减与数据库的交互,并且避免了装载 Address 实体带来的内存开消——这也是 Hibernate 默许启用延迟加载的缘由。
此刻的标题是,延迟加载终究是若何完成的呢? Hibernate 在加载 Person 实体时,Person 实体的 addresses 属性值是甚么呢?
为体会决这个标题,我们在 1 号代码处设置一个断点,在 Eclipse 中中止 Debug,此时能够看到 Eclipse 的 Console 窗口有如图 1 所示的输入:
图 1. 延迟加载调集属性的 Console 输入
正如图 1 输入所看到的,此时 Hibernate 只从 Person 实体对应的数据表中抓取数据,并未从 Address 对象对应的数据表中抓取数据,这就是延迟加载。
那末 Person 实体的 addresses 属性是甚么呢?此时能够从 Eclipse 的 Variables 窗口看到如图 2 所示的成果:
图 2. 延迟加载的调集属性值
从图 2 的方框里的内容能够看出,这个 addresses 属性其实不是我们熟习的 HashSet、TreeSet 等完成类,而是一个 PersistentSet 完成类,这是 Hibernate 为 Set 接口供应的一个完成类。
PersistentSet 调集对象并未真正抓取底层数据表的数据,是以自然也没法真正往初始化调集里的 Address 对象。不外 PersistentSet 调集里持有一个 session 属性,这个 session 属性就是 Hibernate Session,当法式需求拜候 PersistentSet 调集元素时,PersistentSet 就会支配这个 session 属性往抓取理想的 Address 对象对应的数据记实。
那末终究抓取那些 Address 实体对应的数据记实呢?这也难不倒 PersistentSet,由于 PersistentSet 调集里还有一个 owner 属性,该属性就声明了 Address 对象所属的 Person 实体,Hibernate 就会往查找 Address 对应数据表中外键值参照到该 Person 实体的数据。
例如我们单击图 2 所示窗口中 addresses 行,也就是告知 Eclipse 要调试、输入 addresses 属性,这就是要拜候 addresses 属性了,此时便能够在 Eclipse 的 Console 窗口看到输入以下 SQL 语句:
select addresses0_.person_id as person1_0_0_, addresses0_.detail as detail0_, addresses0_.zip as zip0_
from person_address addresses0_
where addresses0_.person_id=?
这就是 PersistentSet 调集跟据 owner 属性往抓取特定 Address 记实的 SQL 语句。此时能够从 Eclipse 的 Variables 窗口看到图 3 所示的输入:
从图 3 能够看出,此时的 addresses 属性已被初始化了,调集里包括了 2 个 Address 对象,这恰是 Person 实体所联络关系的两个 Address 对象。
经由过程下面先容能够看出,Hibernate 对 Set 属性延迟加载关头就在于 PersistentSet 完成类。在延迟加载时,起头 PersistentSet 调集里其实不持有任何元素。但 PersistentSet 会持有一个 Hibernate Session,它能够包管当法式需求拜候该调集时“当即”往加载数据记实,并装进调集元素。
与 PersistentSet 完成类近似的是,Hibernate 还供应了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等完成类,它们的功用与 PersistentSet 的功用大致近似。
熟习 Hibernate 调集属性读者应当记得:Hibernate 请求声明调集属性只能用 Set、List、Map、SortedSet、SortedMap 等接口,而不克不及用 HashSet、ArrayList、HashMap、TreeSet、TreeMap 等完成类,其缘由就是由于 Hibernate 需求对调集属性中止延迟加载,而 Hibernate 的延迟加载是依托 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、 PersistentSortedSet 来完成的——也就是说,Hibernate 底层需求应用自身的调集完成类来完成延迟加载,是以它请求开拓者必需用调集接口、而不是调集完成类来声明调集属性。