一对多关系:使用JPA 2.0更新删除的子项

我有一个双向的一对多关系。

0或1 客户 0个或更多产品订单的列表。

应该在两个实体上设置或取消设置该关系:在客户端,我想设置分配给客户端的产品订单列表; 然后应该将客户端设置/取消设置为自动选择的订单。 在产品订单方面,我想设置分配了oder的客户端; 然后应该从其先前已分配的客户列表中删除该产品订单,并将其添加到新分配的客户列表中。

我想只使用纯JPA 2.0注释和一个“合并”调用实体管理器(使用级联选项)。 我已尝试使用以下代码片段,但它不起作用(我使用EclipseLink 2.2.0作为持久性提供程序)

@Entity public class Client implements Serializable { @OneToMany(mappedBy = "client", cascade= CascadeType.ALL) private List orders = new ArrayList(); public void setOrders(List orders) { for (ProductOrder order : this.orders) { order.unsetClient(); // don't use order.setClient(null); // (ConcurrentModificationEx on array) // TODO doesn't work! } for (ProductOrder order : orders) { order.setClient(this); } this.orders = orders; } // other fields / getters / setters } @Entity public class ProductOrder implements Serializable { @ManyToOne(cascade= CascadeType.ALL) private Client client; public void setClient(Client client) { // remove from previous client if (this.client != null) { this.client.getOrders().remove(this); } this.client = client; // add to new client if (client != null && !client.getOrders().contains(this)) { client.getOrders().add(this); } } public void unsetClient() { client = null; } // other fields / getters / setters } 

持久客户端的门面代码:

 // call setters on entity by JSF frontend... getEntityManager().merge(client) 

用于保持产品订单的外观代码:

 // call setters on entity by JSF frontend... getEntityManager().merge(productOrder) 

在订单端更改客户端分配时,它运行良好:在客户端,订单从先前客户端列表中删除,并添加到新客户端列表(如果重新分配)。

但是当在客户端进行更改时,我只能添加订单 (在订单一侧,执行对新客户端的分配),但是当我从客户端列表中删除订单时它会忽略(在保存和刷新之后,它们仍然在客户端列表,在订单端,它们仍然分配给以前的客户端。

只是为了澄清,我不想使用“删除孤立”选项 :从列表中删除订单时,不应从数据库中删除它,但应更新其客户端分配(即,为null),如在Client#setOrders方法中定义。 怎么能成功呢?


编辑 :感谢我在这里收到的帮助,我能够解决这个问题。 请参阅下面的解决方案

客户端( “一个”/“拥有”一方 )存储已在临时字段中修改的订单。

 @Entity public class Client implements Serializable, EntityContainer { @OneToMany(mappedBy = "client", cascade= CascadeType.ALL) private List orders = new ArrayList(); @Transient private List modifiedOrders = new ArrayList(); public void setOrders(List orders) { if (orders == null) { orders = new ArrayList(); } modifiedOrders = new ArrayList(); for (ProductOrder order : this.orders) { order.unsetClient(); modifiedOrders.add(order); // don't use order.setClient(null); // (ConcurrentModificationEx on array) } for (ProductOrder order : orders) { order.setClient(this); modifiedOrders.add(order); } this.orders = orders; } @Override // defined by my EntityContainer interface public List getContainedEntities() { return modifiedOrders; } 

外观上 ,当持久化时,它会检查是否还有任何必须持久化的实体。 请注意,我使用了一个接口来封装这个逻辑,因为我的外观实际上是通用的。

 // call setters on entity by JSF frontend... getEntityManager().merge(entity); if (entity instanceof EntityContainer) { EntityContainer entityContainer = (EntityContainer) entity; for (Object childEntity : entityContainer.getContainedEntities()) { getEntityManager().merge(childEntity); } } 

JPA没有这样做,据我所知,没有JPA实现可以做到这一点。 JPA要求您管理关系的两个方面。 当只更新关系的一侧时,这有时被称为“对象损坏”

JPA确实在双向关系中定义了一个“拥有”的一方(对于OneToMany,这是没有mappedBy注释的一方),它用于在持久化到数据库时解决冲突(只有一个表示数据库中的关系与内存中的两个相比,因此必须做出解决方案)。 这就是为什么要实现对ProductOrder类的更改,而不是对Client类的更改。

即使拥有“拥有”关系,您也应该始终更新双方。 这通常会导致人们只依赖于更新一方,并且当他们打开二级缓存时会遇到麻烦。 在JPA中,上述冲突仅在持久化并从数据库重新加载对象时解决。 一旦打开第二级缓存,这可能是几个事务,同时你将处理一个损坏的对象。

您还必须合并您删除的订单,仅合并客户端是不够的。

问题在于,虽然您要更改已删除的订单,但您永远不会将这些订单发送到服务器,也永远不会在它们上调用合并,因此您无法反映更改。

您需要在删除的每个订单上调用合并。 或者在本地处理更改,因此您无需序列化或合并任何对象。

EclipseLink确实具有双向关系维护function,在这种情况下可能对您有用,但它不是JPA的一部分。

另一种可能的解决方案是在ProductOrder上添加新属性,我将其命名为以下示例中的detached

如果要从客户端分离订单,可以在订单本身上使用回调:

 @Entity public class ProductOrder implements Serializable { /*...*/ //in your case this could probably be @Transient private boolean detached; @PreUpdate public void detachFromClient() { if(this.detached){ client.getOrders().remove(this); client=null; } } } 

您可以将分离设置为true,而不是删除要删除的订单。 当您合并并刷新客户端时,实体管理器将检测修改的订单并执行@PreUpdate回调,从而有效地从客户端分离订单。