延迟加载与代理模式._第1页
延迟加载与代理模式._第2页
延迟加载与代理模式._第3页
延迟加载与代理模式._第4页
延迟加载与代理模式._第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Hibernate延迟加载剖析与代理模式应用Hibernate的延迟加载(lazy load )是一个被广泛使用的技术。这种延迟加载保证了应用只有在需要时才去数据库中抓取相应的记录。通过延迟加载技术可以避免过多、过早地加载数据表里的数据,从而降低应用的内存开销。Hiber nate的延迟加载本质上就是代理模式的应用,当程序通过Hibernate装载一个实体时,默认情况下, Hibernate并不会立即抓取它的集合属性、关联实体所以对应的记录,而是通过生成一个代理来表示这些集合属性、关联实体,这就是代理模式 应用带来的优势。评论:李冈h自由撰稿人2011年11月17日内容Hibernae的延迟加

2、载是一个非常常用的技术,实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟 加载。Hibernate通过这种延迟加载来降低系统的内存开销,从而保证Hibernate的运行性能。下面先来剖析Hibernate延迟加载的秘密。集合属性的延迟加载当Hibernate从数据库中初始化某个持久化实体时,该实体的集合属性是否随持久化类一起初始化呢?如果集合属性里 包含十万,甚至百万的记录,在初始化持久化实体的同时,完成所有集合属性的抓取,将导致性能急剧下降。完全有可 能系统只需要使用持久化类集合属性中的部分记录,而完全不是集合属性的全部,这样,没有必要一次加载所有的集合 属性。对于集合属性,通

3、常推荐使用延迟加载策略。所谓延迟加载就是等系统需要使用集合属性时才从数据库装载关联的数据。例如下面Person类持有一个集合属性,该集合属性里的元素的类型为Address,该Person类的代码片段如下:清单 1. Person.javapublic class Person/标识属性private Integer id;/ Person 的 name 属性private String name;/保留Person 的age属性private int age;/使用Set来保存集合属性private Set addresses = new HashSet();/下面省略了各属性的setter和

4、getter方法为了让Hibernate能管理该持久化类的集合属性,程序为该持久化类提供如下映射文件:清单 2. Person.hbm.xmlVDOCTYPE hibernate-mapping PUBLIC-/Hibernate/Hibernate Mapping DTD 3.0/EN/dtd/hibernate-mapping-3.0.dtdgenerator class=identity/vproperty name=name type=string/vproperty name=age type=int/从上面映射文件的代码可以看出,Per

5、son的集合属性中的 Address类只是一个普通的POJO。该Address类里包含 detail、zip两个属性。由于 Address类代码非常简单,故此处不再给出该类的代码。上面映射文件中元素里的代码指定了lazy=true(对于元素来说,lazy=true是默认值),它指定Hibernate会延迟加载集合属性里 Address对象。例如通过如下代码来加载ID为1的Person实体:Session session = sf.getCurrentSession();Transaction tx = session.beginTransaction();Person p = (Person)

6、 session.get(Person.class, 1);System.out.println(p.getName();上面代码只是需要访问ID为1的Person实体,并不想访问这个 Person实体所关联的 Address对象。此时有两种情况:Person实体对应的数据记录时立如果不延迟加载,Hibernate就会在加载 即抓取它关联的 Address对象。如果采用延迟加载,Hibernate就只加载Person实体对应的数据记录很明显,第二种做法既能减少与数据库的交互,而且避免了装载Address实体带来的内存开销一一这也是Hibernate默认启用延迟加载的原因。现在的问题是,延迟加载

7、到底是如何实现的呢?Hibernate在加载Person实体时,Person实体的addresses属性值是什么呢?为了解决这个问题,我们在号代码处设置一个断点,在Eclipse中进行 Debug,此时可以看到 Eclipse的Console窗口有如图1所示的输出:图1.延迟加载集合属性的Console 输出如图1输出所看到的,此时 Hibernate只从Person实体对应的数据表中抓取数据,并未从 Address对象对应的数 据表中抓取数据,这就是延迟加载。那么Person实体的addresses属性是什么呢?此时可以从Eclipse的Variables窗口看到如图2所示的结果:图2.延迟

8、加载的集合属性值从图2的方框里的内容可以看出,这个addresses属性并不是我们熟悉的HashSet、TreeSet等实现类,而是一个PersistentSet实现类,这是 Hibernate为Set接口提供的一个实现类。PersistentSet集合对象并未真正抓取底层数据表的数据,因此自然也无法真正去初始化集合里的Address对象。不过PersistentSet集合里持有一个session 属性,这个 session属性就是Hibernate Session,当程序需要访问PersistentSet集合元素时,PersistentSet就会利用这个 session属性去抓取实际的Add

9、ress对象对应的数据记录。那么到底抓取那些 Address实体对应的数据记录呢?这也难不倒PersistentSet,因为PersistentSet集合里还有一个owner属性,该属性就说明了 Address对象所属的 Person实体,Hibernate就会去查找 Address对应数据表中外 键值参照到该Person实体的数据。例如我们单击图 2所示窗口中addresses行,也就是告诉 Eclipse要调试、输出 addresses属性,这就是要访问addresses属性了,此时就可以在 Eclipse的Console窗口看到输出如下 SQL语句:selectaddressesO_.p

10、erson_id as person1_0_0_, addressesO_.detail as detail0_, addresses0_.zip as zip0_fromperson_address addresses0_whereaddressesO_.person_id=?这就是 PersistentSet集合跟据 owner属性去抓取特定Address记录的 SQL语句。此时可以从Eclipse的Variables窗口看到图3所示的输出:图3.已加载的集合属性值2 *2: 巴严砧T 电誓 Q P 口 口Mwnc1 O 缰t O fckJQBCTrjaPfeMKlpan id.2町日0

11、p1 Htdrcin呼MVeeitw- ids 47)org* crazyit* app domainV13可以看出,此时的 addresses属性已经被初始化了,集合里包含了2个Address对象,这正是Person实体所关联的两个 Address对象。通过上面介绍可以看出,Hibernate对于Set属性延迟加载关键就在于PersistentSet实现类。在延迟加载时,开始PersistentSet集合里并不持有任何元素。但PersistentSet会持有一个Hibernate Session,它可以保证当程序需要访问该集合时“立即”去加载数据记录,并装入集合元素。与 Persistent

12、Set 实现类类似的是,Hibernate 还提供了PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等实现类,它们的功能与PersistentSet的功能大致类似。熟悉 Hibernate集合属性读者应该记得:Hibernate要求声明集合属性只能用 Set、List、Map、SortedSet、SortedMap 等接口,而不能用 HashSet、ArrayList、HashMap、TreeSet、TreeMap等实现类,其原因就是因为Hibernate需要对集合属性进行延迟加载,而 Hibernate

13、 的延迟加载是依靠 PersistentSet、PersistentList、PersistentMap、 PersistentSortedMap、PersistentSortedSet 来完成的也就是说,Hibernate底层需要使用自己的集合实现类来完成延迟加载,因此它要求开发者必须用集合接口、而不是集合实现类来声明集合属性。Hibernate对集合属性默认采用延迟加载,在某些特殊的情况下,为set./、list./、map./等元素设置lazy=false属性来取消延迟加载。回页首关联实体的延迟加载默认情况下,Hibernate也会采用延迟加载来加载关联实体,不管是一对多关联、还是一对一

14、关联、多对多关联,Hibernate 默认都会采用延迟加载。对于关联实体,可以将其分为两种情况:关联实体是多个实体时(包括一对多、多对多):此时关联实体将以集合的 形式存在,Hibernate 将使用 PersistentSet、PersistentList、PersistentMap、 PersistentSortedMap、PersistentSortedSet 等集合来管理延迟加载的实体。这 就是前面所介绍的情形。关联实体是单个实体时(包括一对一、多对一):当Hibernate加载某个实 体时,延迟的关联实体将是一个动态生成代理对象。当关联实体是单个实体时,也就是使用 通过lazy属性来

15、指定延迟加载。 或 映射关联实体的情形,这两个元素也可下面例子把 Address类也映射成持久化类,此时Address类也变成实体类,Person实体与Address实体形成一对多的双向关联。此时的映射文件代码如下:清单 3. Person.hbm.xmlVDOCTYPE hibernate-mapping PUBLIC -/Hibernate/Hibernate Mapping DTD 3.0/EN /dtd/hibernate-mapping-3.0.dtdvproperty name=age type=int/接下来程序通过如下代码片段来加载

16、ID为1的Person实体:/打开上下文相关的SessionSession session = sf.getCurrentSession();Transaction tx = session.beginTransaction();Address address = (Address) session.get(Address.class , 1); /System.out.println(address.getDetail();为了看到Hibernate加载Address实体时对其关联实体的处理,我们在号代码处设置一个断点,在Eclipse中进行Debug,此时可以看到 Eclipse的Cons

17、ole窗口输出如下 SQL语句:selectaddressO_.address_id as address1_1_0_,addressO_.detail as detail1_0_,address0_.zip as zip1_0_,addressO_.person_id as person4_1_0_fromaddress_inf address0_whereaddressO_.address_id=?从这条SQL语句不难看出,Hibernate加载Address实体对应的数据表抓取记录,并未从Person实体对应的数据表中抓取记录,这是延迟加载发挥了作用。从Eclipse的Variables

18、窗口看到如图4所示的输出:图4.延迟加载的实体W- Virjiblts 3、 Breakpoints茲拆酉p石q国oSPtcmjO f.iJ=22IB OtoJDBCTrnuctmn id=31iAddressAddrns (k)34.i削addresdd1*一 1Ldetail-广州天再W=38;1eperwmffiid=4S.出g电0 handtfJ4i stLTynrtialiHf 皿=弭!idnull namenullQBif:(id=44) ir4从图4可以清楚地看到,此时 Address实体所关联的 Person实体并不是 Person对象,而是一个Person_$_javassi

19、st_0 类的实例,这个类是 Hibernate 使用 Javassist项目动态生成的代理类当 Hibernate 延迟加载关联实体时,将会采用Javassist生成一个动态代理对象,这个代理对象将负责代理“暂未加载”的关联实体。只要应用程序需要使用“暂未加载”的关联实体,Person_$_javassist_0 代理对象会负责去加载真正的关联实体,并返回实际的关联实体一一这就是最典型的代理模式。单击图4所示Variables窗口中的person属性(也就是在调试模式下强行使用person属性),此时看到Eclipse的Console窗口输出如下的 SQL语句:selectpersonO_.

20、person_id as person1_0_0_, personO_.name as name0_0_, personO_.age as age0_0_fromperson_inf person0_wherepersonO_.person_id=?上面SQL语句就是去抓取“延迟加载”的关联实体的语句。此时可以看到Variables窗口输出图5所示的结果:图5.已加载的实体J略&Value丽 9 sewoftIPnnOt O txjOBCTHritsicboo日 G addressAdcIrHii (id-34)1 detail厂州丟冏“泊=與】日-a: *d =42:id45j怦0 皿畀讯t

21、Aiylni町前览r陆54. idnuJtnultt! 到 p-虹 0660* 吐 44)org * crazy! t app.domain Person(sie5627c*4Hibernate采用“延迟加载”管理关联实体的模式,其实就在加载主实体时,并未真正去抓取关联实体对应数据,而只 是动态地生成一个对象作为关联实体的代理。当应用程序真正需要使用关联实体时,代理对象会负责从底层数据库抓取 记录,并初始化真正的关联实体。在Hibernate的延迟加载中,客户端程序开始获取的只是一个动态生成的代理对象,而真正的实体则委托给代理对象来 管理一一这就是典型的代理模式。回页首代理模式代理模式是一种应

22、用非常广泛的设计模式,当客户端代码需要调用某个对象时,客户端实际上也不关心是否准确得到该 对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy )。在这种设计方式下,系统会为某个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个Java对象代表另一个Java对象来采取行动。在某些情况下,客户端代码不想或不能够直接调用被调用者,代理对象可以在客 户和目标对象之间起到中介的作用。对客户端而言,它不能分辨出代理对象与真实对象的区别,它也无须分辨代理对象和真实对象的区别。客户端代码并不 知道真正的被代理对象,客户端代码面向接口编程,它仅仅持有一个被代理对象的

23、接口。总而言之,只要客户端代码不能或不想直接访问被调用对象一一这种情况有很多原因,比如需要创建一个系统开销很大 的对象,或者被调用对象在远程主机上,或者目标对象的功能还不足以满足需求“,而是额外创建一个代理对象返回给客户端使用,那么这种设计方式就是代理模式。下面示范一个简单的代理模式,程序首先提供了一个Image接口,代表大图片对象所实现的接口,该接口代码如下:清单 3. Image.javapublic interface Imagevoid show();该接口提供了一个实现类,该实现类模拟了一个大图片对象,该实现类的构造器使用Thread.sleep()方法来暂停3s下面是该BigIma

24、ge的程序代码。清单 4. Biglmage.java/使用该BigImage模拟一个很大图片public class BigImage implements Imagepublic BigImage()try/程序暂停3s模式模拟系统开销Thread.sleep(3000);System.out.println(图片装载成功.);catch (InterruptedException ex)ex.printStackTrace();/实现Image里的show()方法public void show()System.out.println( 绘制实际的大图片);上面的程序代码暂停了3s,这表

25、明创建一个 Bigimage对象需要3s的时间开销一一程序使用这种延迟来模拟装载此图片所导致的系统开销。如果不采用代理模式,当程序中创建 Bigimage时,系统将会产生3s的延迟。为了避免这种 延迟,程序为Bigimage对象提供一个代理对象,Bigimage类的代理类如下所示。清单 5. ImageProxy.javapublic class ImageProxy implements Image/组合一个image实例,作为被代理的对象private Image image;/使用抽象实体来初始化代理对象public ImageProxy(Image image)this.image =

26、 image;/*重写Image 接口的show()方法*该方法用于控制对被代理对象的访问,*并根据需要负责创建和删除被代理对象*/public void show()/只有当真正需要调用image的show方法时才创建被代理对象if (image = null)image = new Biglmage();image.show();上面的ImageProxy代理类实现了与 Bigimage相同的show()方法,这使得客户端代码获取到该代理对象之后,可 以将该代理对象当成 Bigimage来使用。在ImageProxy类的show()方法中增加了控制逻辑,这段控制逻辑用于控制当系统真正调用i

27、mage的show()时,才会真正创建被代理的 Bigimage对象。下面程序需要使用 Bigimage对象,但程序并不是直接返回 Bigimage实例, 而是先返回Bigimage的代理对象,如下面程序所示。清单 6. BigimageTest.javapublic class BigimageTestpublic 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();上面程序初始化image非常快,因为程序并未真正创建 BigImage对

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论