- 浏览: 106243 次
- 来自: 北京
文章分类
最新评论
-
^=^:
原来是数字自动转换为对象的意思啊,这样岂不是重用内存变多了。
说说java的自动装箱(autoboxing)和拆箱(unboxing) -
qi90mufeng:
刚好我正需要,谢谢了
hibernate最初印象---helloworld的实现 -
mlc0202:
freedxz 写道好几个 WARN 不处理一下?这个只是当时 ...
hibernate最初印象---helloworld的实现 -
freedxz:
好几个 WARN 不处理一下?
hibernate最初印象---helloworld的实现 -
wzq6578702:
马哥,威武----
感觉路又清晰了
马士兵_JAVA自学之路
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"
- "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>
从上面映射文件的代码可以看出,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());
- 如果不延迟加载,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=?
从图 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 底层需要使用自己的集合实现类来完成延迟加载,因此它要求开发者必须用集合接口、而不是集合实现类来声明集合属性。
Hibernate 对集合属性默认采用延迟加载,在某些特殊的情况下,为 <set.../>、<list.../>、<map.../> 等元素设置 lazy="false"属性来取消延迟加载。
关联实体的延迟加载
默认情况下,Hibernate 也会采用延迟加载来加载关联实体,不管是一对多关联、还是一对一关联、多对多关联,Hibernate 默认都会采用延迟加载。
对于关联实体,可以将其分为两种情况:
- 关联实体是多个实体时(包括一对多、多对多):此时关联实体将以集合的形式存在,Hibernate 将使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等集合来管理延迟加载的实体。这就是前面所介绍的情形。
- 关联实体是单个实体时(包括一对一、多对一):当 Hibernate 加载某个实体时,延迟的关联实体将是一个动态生成代理对象。
当关联实体是单个实体时,也就是使用 <many-to-one.../> 或 <one-to-one.../> 映射关联实体的情形,这两个元素也可通过 lazy 属性来指定延迟加载。
下面例子把 Address 类也映射成持久化类,此时 Address 类也变成实体类,Person 实体与 Address 实体形成一对多的双向关联。此时的映射文件代码如下:
清单 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>
接下来程序通过如下代码片段来加载 ID 为 1 的 Person 实体:
- // 打开上下文相关的 Session
- Session session = sf.getCurrentSession();
- Transaction tx = session.beginTransaction();
- Address address = (Address) session.get(Address.class , 1); //<1>
- System.out.println(address.getDetail());
<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=?
从 Eclipse 的 Variables 窗口看到如图 4 所示的输出:
图 4. 延迟加载的实体
从图 4 可以清楚地看到,此时 Address 实体所关联的 Person 实体并不是 Person 对象,而是一个 Person_$$_javassist_0 类的实例,这个类是 Hibernate 使用 Javassist 项目动态生成的代理类——当 Hibernate 延迟加载关联实体时,将会采用 Javassist 生成一个动态代理对象,这个代理对象将负责代理“暂未加载”的关联实体。
只要应用程序需要使用“暂未加载”的关联实体,Person_$$_javassist_0 代理对象会负责去加载真正的关联实体,并返回实际的关联实体——这就是最典型的代理模式。
单击图 4 所示 Variables 窗口中的 person 属性(也就是在调试模式下强行使用 person 属性),此时看到 Eclipse 的 Console 窗口输出如下的 SQL 语句:
- 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=?
图 5. 已加载的实体
Hibernate 采用“延迟加载”管理关联实体的模式,其实就在加载主实体时,并未真正去抓取关联实体对应数据,而只是动态地生成一个对象作为关联实体的代理。当应用程序真正需要使用关联实体时,代理对象会负责从底层数据库抓取记录,并初始化真正的关联实体。
在 Hibernate 的延迟加载中,客户端程序开始获取的只是一个动态生成的代理对象,而真正的实体则委托给代理对象来管理——这就是典型的代理模式。
代理模式
代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象时,客户端实际上也不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)。
在这种设计方式下,系统会为某个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个 Java 对象代表另一个 Java 对象来采取行动。在某些情况下,客户端代码不想或不能够直接调用被调用者,代理对象可以在客户和目标对象之间起到中介的作用。
对客户端而言,它不能分辨出代理对象与真实对象的区别,它也无须分辨代理对象和真实对象的区别。客户端代码并不知道真正的被代理对象,客户端代码面向接口编程,它仅仅持有一个被代理对象的接口。
总而言之,只要客户端代码不能或不想直接访问被调用对象——这种情况有很多原因,比如需要创建一个系统开销很大的对象,或者被调用对象在远程主机上,或者目标对象的功能还不足以满足需求……,而是额外创建一个代理对象返回给客户端使用,那么这种设计方式就是代理模式。
下面示范一个简单的代理模式,程序首先提供了一个 Image 接口,代表大图片对象所实现的接口,该接口代码如下:
清单 3. Image.java
- public interface Image
- {
- void show();
- }
清单 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
- {
- // 组合一个 image 实例,作为被代理的对象
- private Image image;
- // 使用抽象实体来初始化代理对象
- public ImageProxy(Image image)
- {
- this.image = image;
- }
- /**
- * 重写 Image 接口的 show() 方法
- * 该方法用于控制对被代理对象的访问,
- * 并根据需要负责创建和删除被代理对象
- */
- public void show()
- {
- // 只有当真正需要调用 image 的 show 方法时才创建被代理对象
- if (image == null)
- {
- image = new BigImage();
- }
- image.show();
- }
- }
在 ImageProxy 类的 show() 方法中增加了控制逻辑,这段控制逻辑用于控制当系统真正调用 image 的 show() 时,才会真正创建被代理的 BigImage 对象。下面程序需要使用 BigImage 对象,但程序并不是直接返回 BigImage 实例,而是先返回 BigImage 的代理对象,如下面程序所示。
清单 6. BigImageTest.java
- public class BigImageTest
- {
- public static void main(String[] args)
- {
- long start = System.currentTimeMillis();
- // 程序返回一个 Image 对象,该对象只是 BigImage 的代理对象
- Image image = new ImageProxy(null);
- System.out.println("系统得到 Image 对象的时间开销 :" +
- (System.currentTimeMillis() - start));
- // 只有当实际调用 image 代理的 show() 方法时,程序才会真正创建被代理对象。
- image.show();
- }
- }
图 6. 使用代理模式提高性能
看到如图 6 所示的运行结果,读者应该能认同:使用代理模式提高了获取 Image 对象的系统性能。但可能有读者会提出疑问:程序调用 ImageProxy 对象的 show() 方法时一样需要创建 BigImage 对象啊,系统开销并未真正减少啊?只是这种系统开销延迟了而已啊?
我们可以从如下两个角度来回答这个问题:
- 把创建 BigImage 推迟到真正需要它时才创建,这样能保证前面程序运行的流畅性,而且能减少 BigImage 在内存中的存活时间,从宏观上节省了系统的内存开销。
- 有些情况下,也许程序永远不会真正调用 ImageProxy 对象的 show() 方法——意味着系统根本无须创建 BigImage 对象。在这种情形下,使用代理模式可以显著地提高系统运行性能。
与此完全类似的是,Hibernate 也是通过代理模式来“推迟”加载关联实体的时间,如果程序并不需要访问关联实体,那程序就不会去抓取关联实体了,这样既可以节省系统的内存开销,也可以缩短 Hibernate 加载实体的时间。
小结
Hibernate 的延迟加载(lazy load)本质上就是代理模式的应用,我们在过去的岁月里就经常通过代理模式来降低系统的内存开销、提升应用的运行性能。Hibernate 充分利用了代理模式的这种优势,并结合了 Javassist 或 CGLIB 来动态地生成代理对象,这更加增加了代理模式的灵活性,Hibernate 给这种用法一个新名称:延迟加载。无论怎样,充分分析、了解这些开源框架的实现可以更好的感受经典设计模式的优势所在。
发表评论
-
Hibernate性能优化
2012-05-25 22:23 15251、针对Mysql数据库而 ... -
hibernate效率问题
2012-05-25 21:37 3228关于hibernate效率问题讨论的整理最近在csdn上看到一 ... -
hibernate+proxool
2012-05-14 09:58 886搞了一天的在hibernate ... -
Hibernate性能测试
2012-05-10 10:36 1157在向大家详细介绍Hiberna ... -
用jtds连接SQL2008的方法
2012-05-09 13:15 1796要点: 数据库URL:jdbc:jtds:s ... -
Hibernate 与 Spring 多数据源的配置
2012-05-05 11:34 1104Hibernate 与 Spring 多数据源的配置 S ... -
Hibernate JDBC比较及系统调优
2012-05-04 16:45 1111Hibernate与JDBC比较 ... -
jdbc与hibernate的优缺点比较(转载的精髓)
2012-05-04 16:41 1115一、 Hibernate是JDBC的 ... -
Hibernate访问多个数据库
2012-05-04 11:21 1087本文描述Hibernate访问多个数据库的操作步骤。思路就是, ... -
查询数据库的隔离级别
2012-04-16 11:48 774select @@tx_isolation; 可 ... -
在Hibernate配置文件中设置隔离级别
2012-05-03 21:24 910JDBC连接数据库使用的是默认隔离级别,即读操作已提交(Rea ... -
在Hibernate配置文件中设置隔离级别
2012-05-03 20:30 0JDBC连接数据库使用的是默认隔离级别,即读操作已提交(Rea ... -
hibernate对象的三种状态
2012-03-30 11:07 759Hibernate的透明持久化用起来非常舒服,有时甚至忘记了数 ... -
开发ssh框架程序推荐的目录结构
2012-03-18 06:57 1048开发ssh框架程序推荐 ... -
hibernate的Anotation的应用
2011-12-22 17:14 756A friend is never known till a ... -
hibernate执行流程
2011-12-22 16:13 1864"After you" is good ... -
hibernate最初印象---helloworld的实现
2011-12-22 12:09 2644最近,由于种种原因,开始接触hibernate,今天实现了第一 ...
相关推荐
### Hibernate延迟加载以及利用Spring #### 一、Hibernate延迟加载概念与原理 在理解Hibernate的延迟加载机制之前,我们首先需要了解什么是延迟加载。延迟加载(Lazy Loading)是一种设计模式,其核心思想是在真正...
当Hibernate加载一个实体时,默认不会立即加载其关联的实体或集合属性。相反,它会创建一个代理对象来表示这些属性。当程序试图访问这些属性时,Hibernate会在后台异步加载实际的数据。 #### 三、延迟加载的具体...
详细介绍hibernate延迟加载,对hibernate初学者有一定的帮助
《Hibernate延迟加载与代理模式解析》 在Java的持久化框架Hibernate中,延迟加载(Lazy Load)是一项重要的优化策略,其核心目标是提高系统性能,减少内存占用,避免不必要的数据库交互。延迟加载允许我们在需要...
### Hibernate延迟加载详解 #### 一、什么是延迟加载? 延迟加载是一种优化技术,在软件开发中广泛应用于各种场景,尤其在数据库交互中尤为重要。其核心思想是仅在确实需要某个资源时才加载它,而非一开始就加载...
Hibernate延迟加载机制.zip
【hibernate 延迟加载】 在Java的持久化框架Hibernate中,延迟加载(Lazy Loading)是一种优化数据库访问性能的技术。它允许我们在需要时才加载关联的对象,而不是在初始查询时就一次性加载所有数据。这有助于减少...
《Hibernate延迟加载详解》 Hibernate作为Java领域中的一个强大的对象关系映射框架,提供了许多优化数据库操作的策略,其中之一便是延迟加载(Lazy Loading)。延迟加载机制旨在减少不必要的性能消耗,只在真正需要...
### Hibernate延迟加载详解 #### 什么是Hibernate延迟加载? 在探讨Hibernate延迟加载之前,我们先简要回顾一下Hibernate框架。Hibernate是一种流行的Java持久层框架,它提供了对象关系映射(ORM)的功能,允许...
### Hibernate 延迟加载技术详解 #### 一、概述 在 Hibernate 框架中,延迟加载(Lazy Loading)是一种非常重要的优化技术,它能够有效地减少数据库查询次数,提高应用性能。通常,在多对多或者一对多的关系中,...
### Hibernate的延迟加载详解 #### 实体对象的延迟加载 延迟加载是Hibernate中一项关键的优化技术,旨在减少不必要的数据库交互,从而提升应用程序的性能。其实现原理在于,只有当确实需要访问某个对象或集合的...
### Hibernate延迟加载详解 #### 一、概述 Hibernate 是一种常用的 Java 持久层框架,它能够将 Java 应用程序与底层数据库之间建立桥梁,简化数据访问逻辑。为了提高应用程序性能,Hibernate 提供了多种优化手段,...
Hibernate延迟加载是ORM框架Hibernate中的一个重要特性,它旨在提高应用程序的性能和效率。延迟加载(Lazy Loading)策略允许我们仅在需要访问一个对象或其属性时才从数据库加载它们,而不是在初始加载实体时就一次...
在 Hibernate 框架中,延迟加载(Lazy Loading)是一种优化数据访问性能的重要技术。它允许我们只在真正需要数据时才从数据库加载,避免一次性加载大量数据导致的内存消耗和性能瓶颈。当我们处理与实体相关的集合...
在开发Flex与Hibernate集成的应用时,延迟加载(Lazy Loading)是一个常见的挑战,因为Flex客户端无法直接理解和处理Hibernate的延迟加载机制。延迟加载是一种优化策略,它允许关联的对象在真正需要时才被加载,而...
本案例主要探讨的是Hibernate的延迟加载(Lazy Loading)机制,这是一种优化数据库访问性能的重要策略。延迟加载允许我们在需要数据时才去加载,而不是在初始化对象时一次性加载所有关联数据,从而减少了内存消耗和...