- 译者序
- 前言
- 本书怎么使用
- 本书排版字体约定
- 本书网站
- 致谢
- 第一部分 Hibernate 快速入门
- 第 1 章 安装和设置
- 第 2 章 映射简介
- 第 3 章 驾驭 Hibernate
- 第 4 章 集合与关联
- 第 5 章 更复杂的关联
- 第 6 章 自定义值类型
- 第 7 章 映射标注
- 第 8 章 条件查询
- 第 9 章 浅谈 HQL
- 第二部分 与其他工具的集成
- 第 10 章 将 Hibernate 连接到 MySQL
- 第 11 章 Hibernate 与 Eclipse:Hibernate Tools 使用实战
- 第 12 章 Maven 进阶
- 第 13 章 Spring 入门:Hibernate 与 Spring
- 第 14 章 画龙点睛:用 Stripes 集成 Spring 和 Hibernate
- 附录 A Hibernate 类型
- 附录 B Criteria API
- 附录 C Hibernate SQL 方言
- 附录 D Spring 事务支持
- 附录 E 参考资源
- 作者简介
- 封面介绍
关联的主动加载和延迟加载
先富(rich),而后主动(eager)和延迟(lazy)?这些词听起来好像我们在把数据模型当成鲜活的人物来介绍。但这其实是 O/R 映射的重要主题之一。随着数据模型不断地增长,对象和数据库表之间的关联也会随之增加,程序的功能也不断增多,这很好。但是,通常最后的结果就是成堆的对象之间被链接得零零碎碎。这样,当从一大串彼此相关的对象簇中加载一个从属的对象时,会发生什么?可以看到,只要通过遍历对象的属性,就可以从一个对象访问到关联的另一个对象。这似乎是说,当需要加载某个对象时,就得必须加载相关联的所有对象。对于小型数据库来说,这没有什么问题(事实上,我们在示例中使用的 HSQLDB 数据库只是在运行时,才全部存在于内存中);但是一般情况下,应用程序预定的数据库容量肯定要超过程序自身占用的内存量。哇!但是,即使真能全部加载,也不太可能真正用到大部分数据对象,所以,加载全部对象只是浪费资源而已。
所幸,对象/关系映射软件(包括 Hibernate 在内)的设计者已经预见了这个问题。处理的技巧就是关联配置成"lazy"的,这样,只有在实际需要访问相关联对象的引用时,才会加载相应的对象。Hibernate 将负责维护链接对象的标识,直到真正访问它时,才会加载这个对象。这是我们经常使用的集合在本质上的一个重要特性。
应该怎么做
在 Hibernate 3 发布以前,关联在默认情况下并不设置 lazy 属性,所以必须手工在映射声明中设置 lazy 属性。不过,因为使用延迟加载总是最佳实践,所以 Hibernate 3 就默认保留了 lazy 的设置(进行应用程序移植时需要特别小心,绝不要发生像这种向后不兼容(backward-incompatible)的问题)。
当对象的关联是 lazy 类型时,Hibernate 就使用它自己特殊的 Collections 类的延迟加载实现机制,直到试图真正使用集合的内容时,才从数据库中加载相应的内容。这些处理是完全透明的,所以代码编写者并不会注意到代码背后发生的这些细节。
嗯,如果真是这么简单就能解决加载大量相关对象的问题,那为什么还要将这个设置关掉呢?问题在于,当你关掉 Hibernate 会话时,这种透明性就消失了。在关掉会话后,如果你试图访问尚未初始化的延迟加载集合(即使将这个集合赋值给不同的变量,或者作为方法调用的返回值),Hibernate 提供的代理集合(proxy collection)也将不再访问数据库以延迟加载它的内容,而是强制抛出一个 LazyInitializationException 异常(稍后我们将介绍如果确实需要很快关闭会话时,应该如何处理)。
注意:复杂度守恒看起来很像热力学定律。
因为这种无法预料的异常与 Hibernate 特定的程序代码本身没有什么关系,与加载超过实际使用数量的对象而付出的潜在代价相比,如果你认为加载所有可能需要使用的对象更为重要,那么就可以关掉 lazy 设置。你要负责仔细考虑在什么情况下需要禁用这一设置,如果使用 lazy 设置的话,也要确保安全地使用延迟加载对象。Hibernate 参考手册中深入讨论了这方面的选择策略。
例如,如果我们不想使用延迟初始化,就可以把曲目艺人映射文件按例 5-1 所示的方式进行设置。
例 5-1:在曲目艺人关联中使用主动加载初始化
<set name="artists"table="TRACK_ARTISTS"lazy="false">
<key column="TRACK"/>
<many-to-many class="com.oreilly.hh.data.Artist"column="ARTIST_ID"/>
</set>
其他
集合以外延迟加载的用法?缓存(cache)和集群(cluster)?
延迟加载集合的支持机制很容易理解,因为 Hibernate 提供了它自己对 Collection 接口的实现。
但是,对于其他类型的关联应该如何处理?其他类型的关联也可以受益于按需加载而带来的好处。
事实上,Hibernate 确实支持,用法几乎也是那么简单(至少从服务的使用者来看确实如此)。同样,从 Hibernate 3 开始,类的默认加载方式就是延迟加载,不过,你可以将整个持久化类设置为 lazy="false"来关闭延迟加载(这个属性就放在映射文档的 class 标签中)。
当采用延迟加载的方式来映射一个类时,Hibernate 将生成一个代理类来扩展数据类(data class)。这个延迟代理类会推迟加载数据的时机,直到真正需要使用数据时再加载。任何关联到这个延迟加载类的其他对象都会悄悄地用这样的代理对象进行扩展,而不是引用实际的数据对象。当第一次使用代理对象的任何方法时,才会加载真正的数据对象,并将方法调用委托给数据对象。在加载完成数据对象以后,代理对象将一直把所有的方法调用都委托给它。如果想做的再花哨点,可以使用 proxy 属性来指定代理类将要扩展(或实现)的特定类(或接口)。lazy 属性是将持久化类本身指定为要被代理的类的快捷方式。(如果看不懂,也别担心,这只是说明你现在还不需要这种功能而已。当你需要时,就会懂了!)
自然地,在会话关闭之前才能加载需要使用的任何东西,这一限制依然适用于这种延迟加载类的初始化。你可以使用延迟加载类,但这样做时,一定要小心慎重,并做好规划。
Hibernate 参考文档在它的“提升性能”(Improving Performance)( [1] )一章中深入讨论了这些值得权衡的考虑。也介绍了 Hibernate 甚至可以和 JVM 层或集群对象缓存进行集成,以减轻数据库访问的瓶颈,提升大型分布式应用程序的性能。当集成这样的缓存机制时,可以在映射文档中用 cache 标签(足够恰当)来配置类和关联的缓存行为。这些配置方法不在本书讨论的范围内,但是你应该注意到这些可行的方法,因为说不定你的应用程序就可以从中受益。
令人头痛的问题
忽略对这些主题的讨论,可能不是你欣赏的做法。坦白地说,理解代码执行时的任何路径上需要访问的对象集的边界,在优化加载对象的同时,还不会浪费太多的内存和开发人员的精力,这些问题都是 O/R 映射中存在的最困难的权衡和挑战。甚至像 Hibernate 这样优秀的组件库也不完全屏蔽这些问题。
幸好,有些技术可以用于对数据访问代码进行优化,在许多常见的应用场合中从根本上避免整个问题,从中你可以理解这些技术得以流行的原因。请记住,只有在关闭 Hibernate 会话之后再去访问关联对象时,延迟加载的关联才会出问题。如果在会话打开期间可以完成所有的数据处理,那么延迟加载的关联总是可以正常工作,不必为它们过多担心。
好,那么在程序开始运行时就打开会话,直到程序结束再关闭会话,这样可以吗?哦,不,这样是不行的。因为一个会话就包括了一个数据库事务(transaction),而数据库是共享资源。打开的事务被保留的时间越长,数据库就越可能陷入困境;向其他用户和进程隐藏的活动越多,这些活动就越可能被发现;当你最终提交事务处理时,也就越可能与其他事务遇到冲突。所以,绝对不要在等待用户进行操作的同时而保持打开一个事务。
所以我们岂不就是遇上 Catch-22( [2] )了?其实情况并没有想象中的那么坏。通常,只要应用程序的总体结构设计得合理,就可以让数据访问发生的位置一目了然,也就为 Hibernate 会话提供了开始和结束的自然边界。例如对于 Web 应用程序,在处理到来的请求的过程中打开 Hibernate 会话,这样做很有意义。事实上,开发人员经常建立一些 Servlet 过滤器(filter)来自动完成这些处理。此外,如果有些后台任务需要定期地处理数据(例如在夜间发送电子邮件),它们就可以在类似良好定义的边界内使用各自单独的会话。所以,虽然处理策略需要一定的技巧而且也很重要,但困难也不是不能克服的。我们将在第 13 章和第 14 章用一些具体的示例来演示一些不错的方法。
[1] http://www.hibernate.org/hib_docs/v3/reference/en/html/performance.html. [2] 这个词语源于美国著名作家约瑟夫・海勒的成名作《第 22 条军规》(Catch-22),写于 1961 年,这是一部典型的黑色幽默之作。在当代英语中 Catch-22 作为一个独立的单词,使用频率非常高,用来形容自相矛盾、不合逻辑的规定,或条件所造成的无法脱身或左右为难的困境。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论