Hibernate懒加载应用程序设计

87

我倾向于把HibernateSpring框架结合使用,并利用其声明式事务划分能力(例如,@Transactional)。

众所周知,Hibernate试图尽可能地保持“非侵入性”和“透明性”,但是在使用“延迟加载”关系时,这变得有点更具挑战性


我看到了许多设计方案,其中各种透明级别不同。

  1. 使关系不延迟加载 (例如, fetchType=FetchType.EAGER)
    • 这违反了懒加载的整个理念..
  2. 使用Hibernate.initialize(proxyObj);初始化集合
    • 这意味着与DAO相对较高的耦合
    • 尽管我们可以定义一个带有initialize的接口,但不能保证其他实现提供任何等效内容。
  3. 将事务行为添加到持久Model对象本身上(使用动态代理@Transactional)
    • 我从未尝试过动态代理方法,虽然我似乎从未让@Transactional在持久对象本身上工作。可能是因为Hibernate实际上正在操作一个代理。
    • 失去了控制事务实际的进行时机
  4. 提供懒/非懒API,例如,loadData()loadDataWithDeps()
    • 强制应用程序知道何时采用哪个例程,再次紧密耦合
  5. 方法溢出, loadDataWithA(), ...., loadDataWithX()
  6. 强制查找依赖项,例如仅提供byId()操作
    • 需要很多非面向对象的例程,例如findZzzById(zid),然后是getYyyIds(zid)而不是z.getY()
    • 如果在事务之间存在大量处理开销,则逐个获取集合中的每个对象可能很有用。
  7. 将部分应用程序@Transactional而不仅仅是DAO
    • 可能考虑嵌套事务
    • 需要适应事务管理的例程(例如足够小)
    • 对程序影响较小,尽管可能导致大型交易
  8. 为DAO提供动态提取配置文件,例如loadData(id,fetchProfile);
    • 应用程序必须知道何时使用哪个配置文件
  9. AoP类型的事务,例如拦截操作并在必要时执行事务
    • 需要字节码操作或代理使用
    • 在执行事务时失去控制
    • 黑魔法,一如既往 :)

我有错过任何选项吗?


在尝试最小化应用程序设计中lazy-loaded关系的影响时,您更喜欢哪种方法?

(噢,对于 WoT 很抱歉)


请参考以下网页:http://m-hewedy.blogspot.ch/2010/03/hibernate-failed-to-lazily-initialize.html。 - Adriano
请问您能否提供选项4的示例? - degreesightdc
3个回答

26
众所周知,hibernate试图使自己尽可能地无侵入和透明。但我认为最初的假设是错误的。透明持久性是一种神话,因为应用程序始终应该注意实体的生命周期以及加载的对象图的大小。
请注意,Hibernate无法读取思想,因此,如果您知道需要特定一组依赖项进行特定操作,您需要以某种方式向Hibernate表达您的意图。
从这个角度来看,显式表示这些意图的解决方案(即2、4和7)看起来是合理的,并且不会受到透明度缺失的影响。

当然,你是对的,“尽可能透明”只能做到这个程度。你选择的很好。 - Johan Sjöberg
在我看来:答案非常正确。确实,这是一个误区。顺便说一句:我的选择会是选项4和7(或者干脆不再使用ORM)。 - G. Demecki

7
我不确定你暗示的是哪个(由于懒惰引起的)问题,但对我来说最大的痛苦是避免在自己的应用程序缓存中丢失会话上下文。 典型案例:
- 对象foo被加载并放入映射中; - 另一个线程从地图中取出此对象并调用foo.getBar()(以前从未调用过且为惰性求值); - 爆炸!
因此,为了解决这个问题,我们有一些规则:
- 尽可能透明地包装会话(例如Web应用程序的OpenSessionInViewFilter); - 在线程/线程池的公共API中进行数据库会话绑定/解绑,以便在层次结构较高的位置完成(包装在try / finally中),因此子类不必考虑它; - 在线程之间传递对象时,传递ID而不是对象本身。如果需要,接收线程可以加载对象; - 在缓存对象时,永远不要缓存对象本身,而是它们的ID。在您知道ID时,在DAO或管理器类中具有从第二级Hibernate缓存加载对象的抽象方法。从第二级Hibernate缓存检索对象的成本仍然比访问数据库便宜得多。
正如您所看到的,这确实与“非侵入式和透明”毫不相似。但是成本仍然可以承受,与我必须支付的急切加载价格相比。后者的问题在于,有时会导致蝴蝶效应,即使只加载单个引用对象,更不用说实体集合了。至少内存消耗、CPU使用和延迟也要更糟糕,所以我想我可以接受它。

感谢您的回复。强制应用程序关心加载惰性对象导致了“透明度”的丧失。如果一切都被急切地获取,应用程序就可以完全不知道对象是否已持久化到数据库中,因为Foo.getBar()将始终成功。 “在线程之间传递对象时,请传递ID”,是的,这将对应于#5。 - Johan Sjöberg

3
一个非常常见的模式是,如果你正在构建一个Web应用程序,可以使用OpenEntityManagerInViewFilter
如果你正在构建一个服务,我会在公共服务方法上打开TX,而不是在DAO上。因为很多时候一个方法需要获取或更新多个实体,这将解决任何“懒加载异常”问题。如果你需要更高级的性能调优,我认为可以使用提取配置文件的方式来解决。

1
我猜你想说的是:一个非常常见的反模式...。虽然我同意在服务层打开TX,但使用OSIV仍然是一种反模式,并且会导致非常严重的问题,比如无法优雅地处理异常或性能下降。总之:在我看来OSIV是一种随意的解决方案,但只适用于玩具项目。 - G. Demecki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接