原文:JPA implementation patterns: Bidirectional Associations vs. Lazy loading
作者:Vincent Partington
出处:http://blog.xebia.com/2009/05/25/jpa-implementation-patterns-bidirectional-associations-vs-lazy-loading/
两周之前,我在博客上讲述了服务门面和数据传输对象模式在JPA应用架构中的用法,本周我会移至一个较高的层面,从该角度来讨论一种非常有意思的相互作用,我发现的这一相互作用存在于双向关联的管理方式和延迟加载之间。那么,让我们现在就开始进入这一话题吧。
本篇博客假定你已熟悉我在这一系列的博客文章的首两篇中介绍的Order/OrderLine实例,如果你并未了解的话,那么请先回顾一下该例子。
考虑一下以下代码:
OrderLine orderLineToRemove = orderLineDao.findById(30);
orderLineToRemove.setOrder(null);
此代码的目的是要解除OrderLine与其之前已关联的Order之间的关联,你可能会设想在删除orderLine对象之前先这样做(虽然也可以通过使用@PreRemove注解来让这一步骤自动完成),或者是在想把OrderLine依附到不同的Order实体时会这样做。
如果你运行这一代码的话,你会发现以下的这些实体将会被加载:
1. id为30的OrderLine。
2. 与该OrderLine相关联的Order,这会发生这是因为OrderLine.setOrder方法调用了Order.internalRemoveOrderLine方法来从其父Order对象中删除了OrderLine的缘故。
3. 所有其他与该Order相关联的OrderLine!Order.orderLines集在id为30的OrderLine被从其中删除时被加载。
这一过程可能要用到两个到三个查询,这取决于JPA提供程序(JPA provider),Hibernate使用三个查询;上面提到的每一行内容用到一个。OpenJPA只需要两个查询,因为它使用一个外连接(outer join)来一起加载OrderLine和Order。
不过在这里令人关注的事情是所有的OrderLine实体都被加载了,如果有大量的OrderLine存在的话,这可真是一个代价非常高的操作,因为即使在你并没有对集合的真正内容感兴趣时,集合也被加载了,你为了保持双向关联的完好而正在付出高昂的代价。
到目前为止,我已经发现了三种不同的解决这一问题的方法:
1. 不要把关联设置成双向的;只保持从子对象到父对象的引用,在这种情况下,这将意味着去掉Order对象中的orderLines集。为了检索Order所拥有的OrderLine,你应该调用OrderLineDao的findOrderLinesByOrder方法。因为是检索所有的子对象,于是我们就遇到了这样的一个问题,因为有许多的子对象,这就需要编写更多特定的查询来查找所需的子对象的子集。这种方法的一个缺点是,这意味着如果不通过一个服务层的话,Order就不能够访问它的OrderLine(这是我们在随后的博客中将会解决的一个问题)。
2. 使用Hibernate特有的@LazyCollection注解来促成集合的“格外地延迟(extra lazily)”加载,像这样:
@OneToMany(mappedBy = "order", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.EXTRA)
public Set<OrderLine> orderLines = new HashSet<OrderLine>();
这一功能使得Hibernate能够处理非常大的集合,例如,在你需要集合的大小的时候,Hibernate不会加载集合中的所有元素,作为替代,它会执行一个SELECT COUNT(*) FROM……查询。不过更有意义的是:对集合的修改需要排队等候而不是直接得到作用,如果在集合被访问的时候存在任何悬而未决的修改的话,则在进行进一步的工作之前先要冲刷会话(session)。
对于size()方法来说,这一做法很有效,在你尝试在集合的元素上进行迭代的时候,该做法不会起作用(见这个已被提出两年有半的JIRA问题HHH-2087)。对集合大小的格外延迟加载至少还有两个未解决的bug:HHH-1491和HHH-3319,所有的这些都使我倾向于相信Hibernate的格外延迟加载功能是一个好的想法,但还未完全成熟(吧?)。
3. 由Hibernate的把集合操作延迟直到真正需要它们执行的时候的这一机制激发所想到的,我对Order类进行了修改以便其能够完成类似的事情,首先,操作队列作为一个瞬态域被添加到了Order类中:
private transient Queue<Runnable> queuedOperations = new LinkedList<Runnable>();
然后修改internalAddOrderLine和internalRemoveOrderLine方法,以便它们不直接修改orderLines集,作为替代,它们会创建QueueOperation类的的适当子类的一个实例,使用将被添加或者删除的OrderLine对象来初始化该实例,然后把它放到queuesOperations队列中:
public void internalAddOrderLine(final OrderLine line) {
queuedOperations.offer(new Runnable() {
public void run() { orderLines.add(line); }
});
}
public void internalRemoveOrderLine(final OrderLine line) {
queuedOperations.offer(new Runnable() {
public void run() { orderLines.remove(line); }
});
}
最后修改getOrderLines方法,以便它在返回集合之前执行队列中的所有操作:
public Set<? extends OrderLine> getOrderLines() {
executeQueuedOperations();
return Collections.unmodifiableSet(orderLines);
}
private void executeQueuedOperations() {
for (;;) {
Runnable op = queuedOperations.poll();
if (op == null)
break;
op.run();
}
}
如果有更多的方法需要完全更新的集合的话,那么它们应该以类似的方式调用executeQueuedOperations方法。
这一做法的缺点是领域对象充斥着的 “连接管理代码”比我们在管理双向关联时已有的更多,把这一逻辑提取出来放到一个单独的类中的工作就作为一个练习留给读者。
当然这个问题并非只发生在你有双向关联的时候,它会在任何你操纵使用@OneToMany或者@ManyToMany映射的大型集合的时候浮现,双向关联仅仅是使得这一缘由变得不那么明显,因为你会认为你只不过是在操纵单个实体而已。
2009-5-31的补充:如果你对被映射的集合级联所有操作的话,那么就不应该使用前面描述的第3个方法,如果推迟了集合的修改操作的话,JPA提供程序将不会知晓这要添加或删除的元素,因此会把操作级联到错误的实体上去,这意味着任何被添加到已经设置了@CascadeType.PERSIST的集合中的实体将不会被持久,除非你对它们显式地调用EntityManager.persist。一个类似的说明是,当从Hibernate的PersistentCollection中确实地删除它们时,Hibernate特定的@org.hibernate.annotations.CascadeType.DELETE_ORPHAN注解将只删除孤儿子实体。
无论如何,现在你知道了是什么导致了性能的损失以及三种可能的解决方法,我很想知道你们是否遇到过这种问题,以及你们是如何解决该问题的。
分享到:
相关推荐
"13_传智播客JPA详解"系列教程涵盖了JPA的诸多方面,而"13_传智播客JPA详解_JPA中的多对多双向关联实体定义与注解设置"这一部分则专门聚焦于多对多关联的实践。 在关系型数据库中,多对多关联是最为复杂的一种关系...
在Java Persistence API (JPA) 中,多对多(ManyToMany)关系是表示两个实体之间复杂关联的一种方式。这种关联允许一个实体实例可以与多个其他实体实例相关联,反之亦然。例如,一个学生可以选修多门课程,一门课程...
在Java Persistence API (JPA) 中,一对一双向关联是一种关系映射,它允许两个实体类之间存在一对一的关系,并且每个实体都能引用对方。这种关联是双向的,意味着每个实体都可以通过自己的属性访问到另一个实体。...
然而,需要注意的是,由于Java的反射机制和代理对象的限制,延迟加载在某些情况下可能无法工作,比如在没有JPA上下文的情况下直接访问实体的关联属性。 **关系维护** JPA提供了两种方式来维护一对多关系:`...
在Java Persistence API (JPA) 中,一对多关联是一种常见的关系映射,它表示一个实体可以与多个其他实体相关联。这种关联在数据库层面通常通过外键实现,而在JPA中,我们通过注解来配置这种关系。这篇博客将深入探讨...
本教程“11_传智播客JPA详解_JPA中的一对多延迟加载与关系维护”聚焦于JPA在处理一对多关系时的延迟加载机制以及如何有效地维护这些关系。 一、JPA一对多关系 在数据库中,一对多关系意味着一个实体可以与多个其他...
本资料包"10_JPA详解_JPA中的一对多双向关联与级联操作.zip"聚焦于JPA中的一个重要概念——一对多双向关联及其级联操作。以下是对这一主题的详细阐述。 **一对多关联** 在关系数据库设计中,一对多关联是最常见的...
2. **懒加载与急加载**:JPA提供了懒加载和急加载策略来处理关联对象的加载。默认情况下,一对一关联是急加载,可以通过`@OneToOne(fetch = FetchType.LAZY)`设置为懒加载,以提高性能。 3. **数据一致性**:由于...
本资料“13_JPA详解_JPA中的多对多双向关联实体定义与注解设置”专注于讲解JPA如何处理多对多双向关联关系,这是数据库设计中常见的一种复杂关联类型。下面我们将详细探讨这一主题。 首先,我们需要理解多对多关联...
本文主要探讨的是JPA中的`@OneToOne`注解,它用于建立两个实体之间的一对一关联关系。在实际开发中,这种关联关系常常出现在需要精确映射一对一实体映射的情况,例如用户与个人信息、车辆与车牌号等。我们将会深入...
**JPA 2 一对多双向关联关系** Java Persistence API(JPA)是Java平台上的一个标准,用于处理对象关系映射(ORM),使得开发者可以使用面向对象的方式操作数据库。在JPA中,一对多关联关系是常见的实体间关系类型...
本教程将深入探讨JPA中的一对多双向关联及级联操作,这对于理解和使用JPA进行复杂数据模型管理至关重要。 首先,我们来理解一下一对多关联。在数据库设计中,一对多关联是指一个实体(表)可以与多个其他实体相关联...
在JPA中,多对多(Many-to-Many)关联是一种常见的关系类型,适用于描述两个实体之间复杂的关系。本教程将深入探讨JPA中多对多双向关联的各个方面及其操作。 在数据库中,多对多关联意味着一个实体可以与多个其他...
在这个"06_传智播客JPA详解_大数据字段映射与字段延迟加载"的自学视频中,我们将会深入探讨两个关键概念:大数据字段的映射和字段的延迟加载。 首先,让我们来理解大数据字段映射。在数据库中,有些字段可能包含...
在Java世界中,Java Persistence API(JPA)是用于管理和持久化对象的规范,它使得开发者可以方便地在关系数据库和面向对象的编程之间建立桥梁。JPA通过提供ORM(对象关系映射)机制,让Java开发者能用面向对象的...
标题和描述提到的“hibernate延迟加载解决”主要涉及如何理解和解决与之相关的常见问题。 **1. 延迟加载概念** 延迟加载是一种优化策略,它使得关联的对象在真正需要时才从数据库中加载,而不是在加载主对象时立即...
在处理关联关系时,Spring Data JPA提供了懒加载和急加载(Eager vs Lazy Fetching)机制,通过配置实体类的属性映射,可以在需要时按需加载关联的数据,避免了N+1查询问题。同时,它还支持级联操作(Cascade ...
JPA用于整合现有的ORM技术,可以简化现有Java EE和Java SE应用对象持久化的开发工作,实现ORM的统一。JPA详解视频教程 第13讲 JPA中的多对多双向关联实体定义与注解设置.avi
**JPA(Java Persistence API)**是Java平台上的一个标准,用于对象关系映射(ORM),它简化了数据库与Java应用程序之间的数据交互。在JPA中,多对多(Many-to-Many)关联是一种常见的关系类型,允许一个实体实例与...