我有双向的一对多关系。
0或1 客户< - > 0个或更多产品订单的列表。
应在两个实体上设置或取消设置该关系: 在客户端,我想设置分配给客户端的产品订单列表;然后应该将客户端设置/取消设置为自动选择的订单。 在产品订单方面,我想设置分配了oder的客户端;然后,该产品订单应从其先前已分配的客户列表中删除,并添加到新分配的客户列表中。
我想使用纯JPA 2.0注释和一个" merge"仅调用实体管理器(使用级联选项)。我已尝试使用以下代码片段,但它不起作用(我使用EclipseLink 2.2.0作为持久性提供程序)
@Entity
public class Client implements Serializable {
@OneToMany(mappedBy = "client", cascade= CascadeType.ALL)
private List<ProductOrder> orders = new ArrayList<>();
public void setOrders(List<ProductOrder> 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)
在订单端更改客户端分配时,它运行良好:在客户端,订单从先前客户端列表中删除,并添加到新客户端列表中(如果需要) -assigned)。
但在客户端更改时,我只能添加订单(在订单方面,执行了对新客户端的分配),但是当我从客户端删除订单时它只是忽略了# 39; s列表(保存和刷新后,它们仍然在客户端的列表中,在订单一侧,它们仍然被分配给以前的客户端。
为了澄清,我不想使用&#34;删除orphan&#34;选项:从列表中删除订单时,不应从数据库中删除订单,但应更新其客户端分配(即,为null),如Client#setOrders方法中所定义。如何实现这一目标?
编辑:感谢我在这里收到的帮助,我能够解决这个问题。请参阅下面的解决方案:
客户(&#34; One&#34; /&#34;拥有&#34; side )存储已在临时字段中修改过的订单。
@Entity
public class Client implements Serializable, EntityContainer {
@OneToMany(mappedBy = "client", cascade= CascadeType.ALL)
private List<ProductOrder> orders = new ArrayList<>();
@Transient
private List<ProductOrder> modifiedOrders = new ArrayList<>();
public void setOrders(List<ProductOrder> 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);
}
}
答案 0 :(得分:5)
JPA没有这样做,据我所知,没有JPA实现可以做到这一点。 JPA要求您管理关系的两个方面。当只更新关系的一侧时,这有时被称为&#34;对象损坏&#34;
JPA确实定义了一个&#34;拥有&#34;在一个双向关系中(对于OneToMany,这是没有mappedBy注释的那一方),它用于在持久化到数据库时解决冲突(数据库中只有一个这种关系的表示形式与两个在内存中,所以必须做出决议)。这就是为什么要实现对ProductOrder类的更改,而不是对Client类的更改。
即使拥有&#34;拥有&#34;关系你应该总是更新双方。这通常会导致人们只依赖于更新一方,并且当他们打开二级缓存时会遇到麻烦。在JPA中,只有当对象被持久化并从数据库重新加载时,才会解决上述冲突。打开第二级缓存后,可能会有几笔交易,同时您将处理损坏的对象。
答案 1 :(得分:0)
您还必须合并您删除的订单,仅仅合并客户端是不够的。
问题在于,虽然您正在更改已删除的订单,但您永远不会将这些订单发送到服务器,也永远不会对它们调用合并,因此您无法反映更改。
您需要在删除的每个订单上调用合并。或者在本地处理您的更改,因此您无需序列化或合并任何对象。
EclipseLink确实具有双向关系维护功能,在这种情况下可能对您有用,但它不是JPA的一部分。
答案 2 :(得分:0)
另一种可能的解决方案是在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,而不是删除要删除的订单。何时合并&amp;刷新客户端,实体管理器将检测修改的订单并执行@PreUpdate回调,有效地从客户端分离订单。