名扬数据:集合属性的延迟加载

实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟加载。Hibernate 通过这种延迟加载来降低系统的内存开销,从而保证 Hibernate 运行性能。Hibernae 延迟加载是一个非常常用的技术。>

下面先来剖析 Hibernate 延迟加载的秘密”

集合属性的延迟加载

当 Hibernate 从数据库中初始化某个耐久化实体时,该实体的集合属性是否随耐久化类一起初始化呢?如果集合属性里包含十万,甚至百万的记录,初始化耐久化实体的同时,完成所有集合属性的抓取,将导致性能急剧下降。完全有可能系统只需要使用耐久化类集合属性中的局部记录,而完全不是集合属性的全部,这样,没有必要一次加载所有的集合属性。

对于集合属性,通常推荐使用延迟加载策略。所谓延迟加载就是等系统需要使用集合属性时才从数据库装载关联的数据。

例如下面 Person 类持有一个集合属性,该集合属性里的元素的类型为 A ddress该 Person 类的代码片段如下:

清单 1. Person.java

    public class Person   

      {   

      // 标识属性  

      private Integer id;   

      // Person  name 属性  

      private String name;   

      // 保存 Person  age 属性  

      private int age;   

      // 使用 Set 来保管集合属性  

      private Set<A ddress> addresses = new HashSet<A ddress>;   

      // 下面省略了各属性的 setter 和 getter 方法  

      ...   

      } 

为了让 Hibernate 能管理该持久化类的集合属性,顺序为该耐久化类提供如下映射文件:

清单 2. Person.hbm.xml

    <?xml version="1.0" encoding="GBK"?>   

      <!DOCTYPE hibernate-mapping PUBLIC   

     "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 

     "http://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 class="identity"/>   

      </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 class="A ddress">   

      <!-- 映射普通属性 detail -->   

      <property name="detail"/>   

      <!-- 映射普通属性 zip -->   

      <property name="zip"/>   

      </composite-element>   

      </set>   

      </class>   

      </hibernate-mapping> 

从上面映射文件的代码可以看出,Person 集合属性中的 A ddress 类只是一个普通的 POJO该 A ddress 类里包含 detailzip 两个属性。由于 A ddress 类代码非常简单,故此处不再给出该类的代码。

上面映射文件中 <set.../> 元素里的代码指定了 lazy="true"对于 <set.../> 元素来说,lazy="true"默认值)指定 Hibernate 会延迟加载集合属性里 A ddress 对象。

例如通过如下代码来加载 ID 为 1  Person 实体:

    Session session = sf.getCurrentSess;   

    Transaction tx = session.beginTransact;   

    Person p = Person session.getPerson.class, 1;  //<1>   

    System.out.printlnp.getNam;  

上面代码只是需要访问 ID 为 1  Person 实体,并不想访问这个 Person 实体所关联的 A ddress 对象。此时有两种情况:

如果不延迟加载,Hibernate 就会在加载 Person 实体对应的数据记录时立即抓取它关联的 A ddress 对象。

如果采用延迟加载,Hibernate 就只加载 Person 实体对应的数据记录。

很明显,第二种做法既能减少与数据库的交互,而且防止了装载 A ddress 实体带来的内存开销—这也是 Hibernate 默认启用延迟加载的原因。

现在问题是延迟加载到底是如何实现的呢? Hibernate 加载 Person 实体时,Person 实体的 addresses 属性值是什么呢?

为了解决这个问题, <1>号代码处设置一个断点, Eclipse 中进行 Debug此时可以看到 Eclipse  Console 窗口有如图 1 所示的输出:

图 1. 延迟加载集合属性的 Console 输出

正如图 1 输出所看到此时 Hibernate 只从 Person 实体对应的数据表中抓取数据,并未从 A ddress 对象对应的数据表中抓取数据,这就是延迟加载。

那么 Person 实体的 addresses 属性是什么呢?此时可以从 Eclipse  Variables 窗口看到如图 2 所示的结果:

图 2. 延迟加载的集合属性值

从图 2 方框里的内容可以看出,这个 addresses 属性并不是熟悉的 HashSetTreeSet 等实现类,而是一个 PersistentSet 实现类,这是 Hibernate 为 Set 接口提供的一个实现类。

PersistentSet 集合对象并未真正抓取底层数据表的数据,因此自然也无法真正去初始化集合里的 A ddress 对象。不过 PersistentSet 集合里持有一个 session 属性,这个 session 属性就是 Hibernate Sess当顺序需要访问 PersistentSet 集合元素时,PersistentSet 就会利用这个 session 属性去抓取实际的 A ddress 对象对应的数据记录。

那么到底抓取那些 A ddress 实体对应的数据记录呢?这也难不倒 PersistentSet因为 PersistentSet 集合里还有一个 owner 属性,该属性就说明了 A ddress 对象所属的 Person 实体,Hibernate 就会去查找 A ddress 对应数据表中外键值参照到该 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 属性去抓取特定 A ddress 记录的 SQL 语句。此时可以从 Eclipse  Variables 窗口看到图 3 所示的输出:

 图 3. 已加载的集合属性值

从图 3 可以看出,此时的 addresses 属性已经被初始化了集合里包含了 2 个 A ddress 对象,这正是 Person 实体所关联的两个 A ddress 对象。

通过上面介绍可以看出,Hibernate 对于 Set 属性延迟加载关键就在于 PersistentSet 实现类。延迟加载时,开始 PersistentSet 集合里并不持有任何元素。但 PersistentSet 会持有一个 Hibernate Sess可以保证当顺序需要访问该集合时“立即”去加载数据记录,并装入集合元素。

与 PersistentSet 实现类类似的Hibernate 还提供了 PersistentListPersistentMapPersistentSortedMapPersistentSortedSet 等实现类,功能与 PersistentSet 功能大致类似。

熟悉 Hibernate 集合属性读者应该记得:Hibernate 要求声明集合属性只能用 SetListMapSortedSetSortedMap 等接口,而不能用 HashSetArrayListHashMapTreeSetTreeMap 等实现类,其原因就是因为 Hibernate 需要对集合属性进行延迟加载,而 Hibernate 延迟加载是依靠 PersistentSetPersistentListPersistentMapPersistentSortedMap PersistentSortedSet 来完成的也就是说,Hibernate 底层需要使用自己的集合实现类来完成延迟加载,因此它要求开发者必需用集合接口、而不是集合实现类来声明集合属性。

Hibernate 对集合属性默认采用延迟加载,某些特殊的情况下,为 <set.../><list.../><map.../> 等元素设置 lazy="false"属性来取消延迟加载。