Hibernate延迟加载剖析与代理模式应用
Hibernae 的延迟加载是一个非常常用的技术,实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟加载。Hibernate 通
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
addresses = new HashSet();// 下面省略了各属性的 setter 和 getter 方法...}
为了让 Hibernate 能管理该持久化类的集合属性,程序为该持久化类提供如下映射文件:
清单 2. Person.hbm.xml
从上面映射文件的代码可以看出,Person 的集合属性中的 Address 类只是一个普通的 POJO。该 Address 类里包含 detail、zip 两个属性。由于 Address 类代码非常简单,故此处不再给出该类的代码。
上面映射文件中
例如通过如下代码来加载 ID 为 1 的 Person 实体:
Session session = sf.getCurrentSession();Transaction tx = session.beginTransaction();Person p = (Person) session.get(Person.class, 1);// System.out.println(p.getName());
上面代码只是需要访问 ID 为 1 的 Person 实体,并不想访问这个 Person 实体所关联的 Address 对象。此时有两种情况:
很明显,第二种做法既能减少与数据库的交互,而且避免了装载 Address 实体带来的内存开销——这也是 Hibernate 默认启用延迟加载的原因。
现在的问题是,延迟加载到底是如何实现的呢? Hibernate 在加载 Person 实体时,Person 实体的 addresses 属性值是什么呢?
为了解决这个问题,我们在 号代码处设置一个断点,在 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 对象对应的数据记录。