Hibernate 延迟加载(四)
Hibernae 的延迟加载是一个非常常用的技术,实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟加载。Hibernate 通过这种延迟加载来降低系统的内存开销,从而保证 Hibernate 的运行性能。
下面先来剖析 Hibernate 延迟加载的“秘密”。
集合属性的延迟加载
当 Hibernate 从数据库中初始化某个持久化实体时,该实体的集合属性是否随持久化类一起初始化呢?如果集合属性里包含十万,甚至百万的记录,在初始化持久化实体的同时, 完成所有集合属性的抓取,将导致性能急剧下降。完全有可能系统只需要使用持久化类集合属性中的部分记录,而完全不是集合属性的全部,这样,没有必要一次加 载所有的集合属性。
对于集合属性,通常推荐使用延迟加载策略。所谓延迟加载就是等系统需要使用集合属性时才从数据库装载关联的数据。
Hibernate3版本:通过asm和cglib二个包实现;Domain是非final的。
Hibernate4版本:通过javassist包实现;
1.session.load懒加载。
2.one-to-one(元素)懒加载:
必需同时满足下面三个条件时才能实现懒加载
(主表不能有constrained=true,所以主表没有懒加载)
lazy!=false 2)constrained=true 3)fetch=select
3.one-to-many (元素)懒加载:1)lazy!=false 2)fetch=select
4.many-to-one (元素) :1)lazy!=false 2)fetch=select
5.many-to-many (元素) :1)lazy!=false 2)fetch=select
6.能够懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象(代理对象)的属性(getId和getClass除外)hibernate会初始化这些代理,或用Hibernate.initialize(proxy)来初始化代理对象;当相关联的session关闭后,再访问懒加载的对象将出现异常。
例如下面 Person 类持有一个集合属性,该集合属性里的元素的类型为 Address,该 Person 类的代码片段如下:
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 方法 ... }
<?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="Address">
<!-- 映射普通属性 detail -->
<property name="detail"/>
<!-- 映射普通属性 zip -->
<property name="zip"/>
</composite-element>
</set>
</class>
</hibernate-mapping>
Session session = sf.getCurrentSession();
Transaction tx = session.beginTransaction();
Person p = (Person) session.get(Person.class, 1); //<1>
System.out.println(p.getName());
如果不延迟加载,Hibernate 就会在加载 Person 实体对应的数据记录时立即抓取它关联的 Address 对象。
如果采用延迟加载,Hibernate 就只加载 Person 实体对应的数据记录。
<1>
号代码处设置一个断点,在 Eclipse 中进行 Debug,此时可以看到 Eclipse 的 Console 窗口有如图 1 所示的输出:图 1. 延迟加载集合属性的 Console 输出
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. 已加载的集合属性值
关 联实体是多个实体时(包括一对多、多对多):此时关联实体将以集合的形式存在,Hibernate 将使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、 PersistentSortedSet 等集合来管理延迟加载的实体。这就是前面所介绍的情形。
关联实体是单个实体时(包括一对一、多对一):当 Hibernate 加载某个实体时,延迟的关联实体将是一个动态生成代理对象。
清单 3. Person.hbm.xml
<?xml version="1.0" encoding="GBK"?>
<!-- 指定 Hibernate 的 DTD 信息 -->
<!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"/>
<!-- 映射集合属性,集合元素是其他持久化实体
没有指定 cascade 属性,指定不控制关联关系 -->
<set name="addresses" inverse="true">
<!-- 指定关联的外键列 -->
<key column="person_id"/>
<!-- 用以映射到关联类属性 -->
<one-to-many class="Address"/>
</set>
</class>
<!-- 映射 Address 持久化类 -->
<class name="Address" table="address_inf">
<!-- 映射标识属性 addressId -->
<id name="addressId" column="address_id">
<!-- 指定主键生成器策略 -->
<generator class="identity"/>
</id>
<!-- 映射普通属性 detail -->
<property name="detail"/>
<!-- 映射普通属性 zip -->
<property name="zip"/>
<!-- 必须指定列名为 person_id,
与关联实体中 key 元素的 column 属性值相同 -->
<many-to-one name="person" class="Person"
column="person_id" not-null="true"/>
</class>
</hibernate-mapping>
Session session = sf.getCurrentSession();
Transaction tx = session.beginTransaction();
Address address = (Address) session.get(Address.class , 1); //<1>
System.out.println(address.getDetail());
为了看到 Hibernate 加载 Address 实体时对其关联实体的处理,我们在
<1>
号代码处设置一个断点,在 Eclipse 中进行 Debug,此时可以看到 Eclipse 的 Console 窗口输出如下 SQL 语句:select
address0_.address_id as address1_1_0_,
address0_.detail as detail1_0_,
address0_.zip as zip1_0_,
address0_.person_id as person4_1_0_
from
address_inf address0_
where
address0_.address_id=?
select
person0_.person_id as person1_0_0_,
person0_.name as name0_0_,
person0_.age as age0_0_
from
person_inf person0_
where
person0_.person_id=?
上面 SQL 语句就是去抓取“延迟加载”的关联实体的语句。此时可以看到 Variables 窗口输出图 5 所示的结果:
图 5. 已加载的实体
清单 3. Image.java
public interface Image { void show(); }
该接口提供了一个实现类,该实现类模拟了一个大图片对象,该实现类的构造器使用 Thread.sleep() 方法来暂停 3s。下面是该 BigImage 的程序代码:
清单 4. BigImage.java
// 使用该 BigImage 模拟一个很大图片
public class BigImage implements Image
{
public BigImage()
{
try
{
// 程序暂停 3s 模式模拟系统开销
Thread.sleep(3000);
System.out.println("图片装载成功 ...");
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
// 实现 Image 里的 show() 方法
public void show()
{
System.out.println("绘制实际的大图片");
}
清单 5. ImageProxy.java
public class ImageProxy implements Image{
// 组合一个 p_w_picpath 实例,作为被代理的对象
private Image p_w_picpath;
// 使用抽象实体来初始化代理对象
public ImageProxy(Image p_w_picpath)
{
this.p_w_picpath = p_w_picpath;
}
public void show()
{
// 只有当真正需要调用 p_w_picpath 的 show 方法时才创建被代理对象
if (p_w_picpath == null)
{
p_w_picpath = new BigImage();
}
p_w_picpath.show();
}
}
清单 6. BigImageTest.java
public class BigImageTest
{
public static void main(String[] args)
{
long start = System.currentTimeMillis();
// 程序返回一个 Image 对象,该对象只是 BigImage 的代理对象
Image p_w_picpath = new ImageProxy(null);
System.out.println("系统得到 Image 对象的时间开销 :" +
(System.currentTimeMillis() - start));
// 只有当实际调用 p_w_picpath 代理的 show() 方法时,程序才会真正创建被代理对象。
p_w_picpath.show();
}
图 6. 使用代理模式提高性能
把创建 BigImage 推迟到真正需要它时才创建,这样能保证前面程序运行的流畅性,而且能减少 BigImage 在内存中的存活时间,从宏观上节省了系统的内存开销。
有些情况下,也许程序永远不会真正调用 ImageProxy 对象的 show() 方法——意味着系统根本无须创建 BigImage 对象。在这种情形下,使用代理模式可以显著地提高系统运行性能。
转载于:https://blog.51cto.com/longx/1357125