Hibernae 的延迟加载是一个非常常用的技术实体的集合属性默认会被延迟加载实体所关联的实体默认也会被延迟加载Hibernate 通过这种延迟加载来降低系统的内存开销从而保证 Hibernate 的运行性能 下面先来剖析 Hibernate 延迟加载的秘密 集合属性的延迟加载 当 Hibernate 从数据库中初始化某个持久化实体时该实体的集合属性是否随持久化类一起初始化呢?如果集合属性里包含十万甚至百万的记录在初始化持久化实体的同时完成所有集合属性的抓取将导致性能急剧下降完全有可能系统只需要使用持久化类集合属性中的部分记录而完全不是集合属性的全部这样没有必要一次加载所有的集合属性 对于集合属性通常推荐使用延迟加载策略所谓延迟加载就是等系统需要使用集合属性时才从数据库装载关联的数据 例如下面 Person 类持有一个集合属性该集合属性里的元素的类型为 Address该 Person 类的代码片段如下 清单 Personjava public class Person { // 标识属性 private Integer id; // Person 的 name 属性 private String name; // 保留 Person 的 age 属性 private int age; // 使用 Set 来保存集合属性 private Set addresses = new HashSet() // 下面省略了各属性的 setter 和 getter 方法 … } 为了让 Hibernate 能管理该持久化类的集合属性程序为该持久化类提供如下映射文件 清单 Personhbmxml xml version= encoding=GBK?> DOCTYPE hibernatemapping PUBLIC //Hibernate/Hibernate Mapping DTD //EN /dtd/hibernatemappingdtd> <hibernatemapping package=orgcrazyitappdomain> <class name=Person table=person_inf> <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/> <compositeelement class=Address> <property name=detail/> <property name=zip/> compositeelement> set> class> hibernatemapping> 从上面映射文件的代码可以看出Person 的集合属性中的 Address 类只是一个普通的 POJO该 Address 类里包含 detailzip 两个属性由于 Address 类代码非常简单故此处不再给出该类的代码 上面映射文件中 元素里的代码指定了 lazy=true(对于 元素来说lazy=true是默认值)它指定 Hibernate 会延迟加载集合属性里 Address 对象 例如通过如下代码来加载 ID 为 的 Person 实体 Session session = sfgetCurrentSession() Transaction tx = sessionbeginTransaction() Person p = (Person) sessionget(Personclass ) //<> Systemoutprintln(pgetName()) 上面代码只是需要访问 ID 为 的 Person 实体并不想访问这个 Person 实体所关联的 Address 对象此时有两种情况 如果不延迟加载Hibernate 就会在加载 Person 实体对应的数据记录时立即抓取它关联的 Address 对象 如果采用延迟加载Hibernate 就只加载 Person 实体对应的数据记录 很明显第二种做法既能减少与数据库的交互而且避免了装载 Address 实体带来的内存开销这也是 Hibernate 默认启用延迟加载的原因 现在的问题是延迟加载到底是如何实现的呢? Hibernate 在加载 Person 实体时Person 实体的 addresses 属性值是什么呢? 为了解决这个问题我们在 <>号代码处设置一个断点在 Eclipse 中进行 Debug此时可以看到 Eclipse 的 Console 窗口有如图 所示的输出 图 延迟加载集合属性的 Console 输出 正如图 输出所看到的此时 Hibernate 只从 Person 实体对应的数据表中抓取数据并未从 Address 对象对应的数据表中抓取数据这就是延迟加载 那么 Person 实体的 addresses 属性是什么呢?此时可以从 Eclipse 的 Variables 窗口看到如图 所示的结果 图 延迟加载的集合属性值 从图 的方框里的内容可以看出这个 addresses 属性并不是我们熟悉的 HashSetTreeSet 等实现类而是一个 PersistentSet 实现类这是 Hibernate 为 Set 接口提供的一个实现类 PersistentSet 集合对象并未真正抓取底层数据表的数据因此自然也无法真正去初始化集合里的 Address 对象不过 PersistentSet 集合里持有一个 session 属性这个 session 属性就是 Hibernate Session当程序需要访问 PersistentSet 集合元素时PersistentSet 就会利用这个 session 属性去抓取实际的 Address 对象对应的数据记录 那么到底抓取那些 Address 实体对应的数据记录呢?这也难不倒 PersistentSet因为 PersistentSet 集合里还有一个 owner 属性该属性就说明了 Address 对象所属的 Person 实体Hibernate 就会去查找 Address 对应数据表中外键值参照到该 Person 实体的数据 例如我们单击图 所示窗口中 addresses 行也就是告诉 Eclipse 要调试输出 addresses 属性这就是要访问 addresses 属性了此时就可以在 Eclipse 的 Console 窗口看到输出如下 SQL 语句 select addresses_person_id as person___ addresses_detail as detail_ addresses_zip as zip_ from person_address addresses_ where addresses_person_id=? 这就是 PersistentSet 集合跟据 owner 属性去抓取特定 Address 记录的 SQL 语句此时可以从 Eclipse 的 Variables 窗口看到图 所示的输出 图 已加载的集合属性值
从图 可以看出此时的 addresses 属性已经被初始化了集合里包含了 个 Address 对象这正是 Person 实体所关联的两个 Address 对象 通过上面介绍可以看出Hibernate 对于 Set 属性延迟加载关键就在于 PersistentSet 实现类在延迟加载时开始 PersistentSet 集合里并不持有任何元素但 PersistentSet 会持有一个 Hibernate Session它可以保证当程序需要访问该集合时立即去加载数据记录并装入集合元素 与 PersistentSet 实现类类似的是Hibernate 还提供了 PersistentListPersistentMapPersistentSortedMapPersistentSortedSet 等实现类它们的功能与 PersistentSet 的功能大致类似 熟悉 Hibernate 集合属性读者应该记得Hibernate 要求声明集合属性只能用 SetListMapSortedSetSortedMap 等接口而不能用 HashSetArrayListHashMapTreeSetTreeMap 等实现类其原因就是因为 Hibernate 需要对集合属性进行延迟加载而 Hibernate 的延迟加载是依靠 PersistentSetPersistentListPersistentMapPersistentSortedMapPersistentSortedSet 来完成的也就是说Hibernate 底层需要使用自己的集合实现类来完成延迟加载因此它要求开发者必须用集合接口而不是集合实现类来声明集合属性 Hibernate 对集合属性默认采用延迟加载在某些特殊的情况下为 等元素设置 lazy=false属性来取消延迟加载
|