原文:JPA implementation patterns: Bidirectional Associations
作者:Vincent Partington
出处:http://blog.xebia.com/2009/03/16/jpa-implementation-patterns-bidirectional-assocations/
上周我们通过数据访问对象模式开始了对JPA实施模式的探索,本周我们继续研究另一个麻烦的问题。
JPA提供了@OneToMany、@ManyToOne、@OneToOne和@ManyToMany等注解来映射对象之间的关联,而EJB 2.x则是提供了容器管理的关联关系(container managed relationships)来管理这些关联,特别是要保持双向关联的同步,JPA留出了更多由开发者来决定的空间。
设置
我们先使用一个OrderLine对象来扩充前一篇博客中的Order例子,它有编号(id)、说明(description)、价格(price)和一个到包含了它的order的引用:
@Entity
public class OrderLine {
@Id
@GeneratedValue
private int id;
private String description;
private int price;
@ManyToOne
private Order order;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public int getPrice() { return price; }
public void setPrice(int price) { this.price = price; }
public Order getOrder() { return order; }
public void setOrder(Order order) { this.order = order; }
}
通过使用泛型DAO模式,我们很快就得到一个非常基本的OrderLineDao接口和一个实现:
public interface OrderLineDao extends Dao<Integer, OrderLine> {
public List<OrderLine> findOrderLinesByOrder(Order o);
}
public class JpaOrderLineDao extends JpaDao<Integer, OrderLine> implements
OrderLineDao {
public List<OrderLine> findOrderLinesByOrder(Order o) {
Query q = entityManager.createQuery("SELECT e FROM "
+ entityClass.getName() + " e WHERE order = :o");
q.setParameter("o", o);
return (List<OrderLine>) q.getResultList();
}
}
我们可以使用这一DAO来把订单项(orderline)添加到订单(order)中,或者是从一个订单中找出所有的订单项。
OrderLine line = new OrderLine();
line.setDescription("Java Persistence with Hibernate");
line.setPrice(5999);
line.setOrder(o);
orderLineDao.persist(line);
Collection<OrderLine> lines = orderLineDao.findOrderLinesByOrder(o);
关联越多问题越多
所有这一切都非常简单,不过如果我们把这一关联变成双向的话,事情就开始有些意思了。让我们在Order对象中添加一个orderLines域,并加入单纯的getter/setter方法对的实现。
@OneToMany(mappedBy = "order")
private Set<OrderLine> orderLines = new HashSet<OrderLine>();
public Set<OrderLine> getOrderLines() { return orderLines; }
public void setOrderLines(Set<OrderLine> orderLines) { this.orderLines = orderLines; }
@OneToMany注解中的mappedBy域被用来告知JPA这是关联的另一端,因此它可以通过查看OrderLine对象的order域来获知其与哪一个Order对象相配,而不是把该域直接映射到数据库中的列上。
因此在不改变底层数据库的情况下,我们现在可以检索订单(order)的订单项(orderline),如下所示:
Collection<OrderLine> lines = o.getOrderLines();
不再需要访问OrderLineDao。J
但是这里存在着一个圈套!虽然EJB 2.x定义的容器管理的关联关系(CMR)确保在添加OrderLine对象到Order的OrderLines属性中的时候,也设置了该OrderLine的order属性,但是JPA(作为一个POJO框架)并没有施展这样的魔法。
这实际上是一件好事,因为这使得我们的领域模型在JPA容器之外依然可用,这意味着你能够更容易地对它们进行测试,以及在它们还没有被持久时就可以使用他们。不过这对那些习惯了EJB 2.x CMR做法的人来说,可能也会感到有些混淆。
如果在单独的事务中运行前面的例子,你会发现他们运行正常,但是如果在类似以下代码实现的事务中运行它们的话,你会发现订单项的列表却是空的:
Order o = new Order();
o.setCustomerName("Mary Jackson");
o.setDate(new Date());
OrderLine line = new OrderLine();
line.setDescription("Java Persistence with Hibernate");
line.setPrice(5999);
line.setOrder(o);
System.out.println("Items ordered by " + o.getCustomerName() + ": ");
Collection<OrderLine> lines = o.getOrderLines();
for (OrderLine each : lines) {
System.out.println(each.getId() + ": " + each.getDescription()
+ " at $" + each.getPrice());
}
这可以通过在第一个System.out.println语句之前添加以下一行来修正:
o.getOrderLines().add(line);
改进再改进……
例子可用,但是不够完美,其破坏了抽象,而且脆弱,因为它依赖于领域对象的用户正确地调用这些setter方法和adder方法。我们可以通过把这些调用移到OrderLine.setOrder(Order)的定义中来解决这一问题:
public void setOrder(Order order) {
this.order = order;
order.getOrderLines().add(this);
}
更好的做法是可以以一种更好的方式来封装Order对象的orderLines属性:
public Set<OrderLine> getOrderLines() { return orderLines; }
public void addOrderLine(OrderLine line) { orderLines.add(line); }
然后按照以下方式重新定义OrderLine.setOrder(Order):
public void setOrder(Order order) {
this.order = order;
order.addOrderLine(this);
}
还理解我的意思吗?希望如此,如果没有跟上我的想法的话,请你自己试试看。
现在另一个问题出现了,如果有人直接调用Order.addOrderLine(OrderLine)方法会怎样呢?OrderLine会被添加到orderLines集合中,但是它的order属性并没有指向它所属的订单(order)。像以下这样来修改Order.addOrderLine (OrderLine)是无效的,因为这将会导致addOrderLine调用setOrder,setOrder反过来调用addOrderLine,addOrderLine又调用setOrder这样一个一直下去的无限循环。
public void addOrderLine(OrderLine line) {
orderLines.add(line);
line.setOrder(this);
}
这个问题可以通过引入一个Order.internalAddOrderLine(OrderLine)方法来解决,该方法添加订单项(line)到集合中,但不调用line.setOrder(this)。OrderLine.setOrder(Order)随后会调用该方法,这样就不会导致一个无限循环。Order类的用户应该调用Order.addOrderLine(OrderLine)。
模式
可以预料采取这一想法得到的结果是,OrderLine类最终的方法是这样的:
public Order getOrder() { return order; }
public void setOrder(Order order) {
if (this.order != null) { this.order.internalRemoveOrderLine(this); }
this.order = order;
if (order != null) { order.internalAddOrderLine(this); }
}
以及Order类最终的方法是这样的:
public Set<OrderLine> getOrderLines() { return Collections.unmodifiableSet(orderLines); }
public void addOrderLine(OrderLine line) { line.setOrder(this); }
public void removeOrderLine(OrderLine line) { line.setOrder(null); }
public void internalAddOrderLine(OrderLine line) { orderLines.add(line); }
public void internalRemoveOrderLine(OrderLine line) { orderLines.remove(line); }
这些方法提供了对创建在EJB 2.x之内的CMR逻辑的一种基于POJO的实现,有着带有典型POJO特征的易于理解、测试和维护的优点。
当然关于这一主题会存在一些变数:
如果Order和OrderLine位于同样的包中的话,那么可以赋予内部的(internal…)方法封装范围来防止它们无意中被调用。(这就是C++的友元类(friend class)概念可派上用场的地方,再说一次,别用它。)
如果永远都不需要从订单中删除订单单项的话,那么可以去掉removeOrderLine和internalRemoveOrderLine方法。
基本倒转一下想法,可以从OrderLine.setOrder(Order)方法中去掉负责管理到Order类的双向关联的部分,不过那就意味着要通过addOrderLine和removeOrderLine方法来传播逻辑了。
作为替代,或者是另外再使用Collections.singletonSet来把orderLine设置成在运行时是只读的,还可以使用泛型类型来使得它在编译时是只读的。
public Set<? extends OrderLine> getOrderLines() { return Collections.unmodifiableSet(orderLines); }
不过,这会使得更难以通过EasyMock一类的模拟框架来模拟这些对象。
在使用这一模式时还有一些事情要考虑:
添加一个OrderLine到Order中并不会自动持久它,你还需要调用其DAO(或者EntityManager)的持久方法来完成这一工作,或者可以通过设置Order.orderLines属性上的@OneToMany注解的cascade属性的值(至少)为CascadeType.PERSIST来达到这一目的。我们在讨论EntityManager.persist方法时会谈到更多关于这方面的情况。
双向关联与EntityManager.merge方法之间的配合不是很好,我们会在进入游离对象这一主题时讨论该问题。
当一个作为双向关联的组成部分的实体(即将)被删除时,也应该从该关联的另一端中去掉它,在我们讨论EntityManager.remove方法时,这种情况也会提到。
以上模式只有在使用域访问(field access)(而不是属性/方法访问)来让你的JPA提供程序填充实体时才有效,当实体的@Id注解被放置在相应域而不是放在相应getter方法之上时,就是使用了域访问。
最后但并非是最不重要的是,尽管该模式可能是一个技术可靠的基于POJO的管理关联的实现,不过你可能会争辩说为什么会需要所有的这些getter和setter方法?为什么既要能使用Order.addOrderLine(OrderLine)又要能使用OrderLine.setOrder来获得相同的结果呢?去掉其中的一个可使得我们的代码变得更简单些,比如可看一下James Holub的关于getter和setter方法的文章。还有就是,我们发现这一模式赋予了使用这些领域对象的开发者按照他们的意愿关联对象的灵活性。
那么,今天到这就结束了,我非常有兴趣听取你们关于这一模式的意见以及你们是如何管理JPA领域对象中的双向关联的。当然,我们会在探讨下一个模式时再见!
分享到:
相关推荐
"13_传智播客JPA详解"系列教程涵盖了JPA的诸多方面,而"13_传智播客JPA详解_JPA中的多对多双向关联实体定义与注解设置"这一部分则专门聚焦于多对多关联的实践。 在关系型数据库中,多对多关联是最为复杂的一种关系...
在Java Persistence API (JPA) 中,一对一双向关联是一种关系映射,它允许两个实体类之间存在一对一的关系,并且每个实体都能引用对方。这种关联是双向的,意味着每个实体都可以通过自己的属性访问到另一个实体。...
在这个主题中,我们将深入探讨如何在JPA中定义和配置双向多对多关联实体,并通过注解进行设置。 首先,我们需要了解JPA中的几个关键注解: 1. `@Entity`:标记一个类作为JPA的实体类,使得该类可以被JPA管理并映射...
这篇博客将深入探讨JPA中的一对多双向关联以及级联操作。 首先,让我们理解一对多关联。假设我们有两个实体,一个是`User`(用户)和一个是`Post`(帖子)。一个用户可以发布多个帖子,但一个帖子只能属于一个用户...
我们将会深入理解JPA如何处理双向一对一关联,并通过一个示例来阐述其工作原理。 **双向一对一关联的定义** 在JPA中,`@OneToOne`注解可以用来表示一个实体类对应另一个实体类的唯一实例。而双向一对一关联意味着...
**JPA 2 一对多双向关联关系** Java Persistence API(JPA)是Java平台上的一个标准,用于处理对象关系映射(ORM),使得开发者可以使用面向对象的方式操作数据库。在JPA中,一对多关联关系是常见的实体间关系类型...
本资料“13_JPA详解_JPA中的多对多双向关联实体定义与注解设置”专注于讲解JPA如何处理多对多双向关联关系,这是数据库设计中常见的一种复杂关联类型。下面我们将详细探讨这一主题。 首先,我们需要理解多对多关联...
本资料包"10_JPA详解_JPA中的一对多双向关联与级联操作.zip"聚焦于JPA中的一个重要概念——一对多双向关联及其级联操作。以下是对这一主题的详细阐述。 **一对多关联** 在关系数据库设计中,一对多关联是最常见的...
**JPA(Java Persistence API)**是Java平台上的一个标准,用于管理关系数据库中的数据,它简化了数据库操作,提供了一种面向对象的方式来处理数据库事务。JPA通过ORM(Object-Relational Mapping)映射机制将Java...
本教程将深入探讨JPA中的一对多双向关联及级联操作,这对于理解和使用JPA进行复杂数据模型管理至关重要。 首先,我们来理解一下一对多关联。在数据库设计中,一对多关联是指一个实体(表)可以与多个其他实体相关联...
本资料主要探讨了JPA中的一对一双向关联,这是一个重要的概念,对于理解和优化数据模型设计至关重要。 一对一双向关联是指在两个实体类中,每个实体都可以直接引用对方,形成双向的关系。例如,一个学生可以对应一...
本教程将深入探讨JPA中多对多双向关联的各个方面及其操作。 在数据库中,多对多关联意味着一个实体可以与多个其他实体相关联,反之亦然。例如,学生和课程之间的关系就是一个典型的多对多关系:一个学生可以选修多...
本讲解将深入探讨JPA中的一对一双向关联特性,帮助开发者更好地理解和应用这一功能。 一对一双向关联是指在两个实体类之间存在一对一的关系,并且双方都能直接访问对方。这种关联关系在现实世界的例子中很常见,...
在处理关联关系时,Spring Data JPA提供了懒加载和急加载(Eager vs Lazy Fetching)机制,通过配置实体类的属性映射,可以在需要时按需加载关联的数据,避免了N+1查询问题。同时,它还支持级联操作(Cascade ...
Spring Data JPA 在 Spring Boot 项目中的集成与应用 Spring Data JPA 是一款基于 Java 持久层 API(JPA)的框架,提供了一个通用的数据访问层解决方案,能够简化 Java 应用程序中的数据访问工作。Spring Boot 作为...
一共有三个分卷。全部下载才能解压。 这本书不错,值得一看。
后来,尝试使用 Hibernate 或 JPA 等框架搜索具有相同值的列失败,因为搜索词没有填充到n ,导致搜索词与列中存储的值不匹配。 例如,考虑以下表定义: CREATE TABLE CITY ( ... NAME CHAR(40) NOT NULL ...);如果...
1:JPA: : 2:对象数据库: ://www.objectdb.com/ 3:JPA 性能基准: ://www.jpab.org/All/All/All.html 支持功能 支持所有蓝图功能, 除外 支撑 支持 支持 Java 5、6 或 7 支持 JPA 你需要哪一个取决于你想用...
JPA用于整合现有的ORM技术,可以简化现有Java EE和Java SE应用对象持久化的开发工作,实现ORM的统一。JPA详解视频教程 第13讲 JPA中的多对多双向关联实体定义与注解设置.avi