[英]One-to-many relationship: Update removed children with 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<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)
在訂單端更改客戶端分配時,它運行良好:在客戶端,訂單從先前客戶端列表中刪除,並添加到新客戶端列表(如果重新分配)。
但是當在客戶端進行更改時,我只能添加訂單 (在訂單一側,執行對新客戶端的分配),但是當我從客戶列表中刪除訂單時它會忽略(在保存和刷新之后,它們仍然在客戶端列表,在訂單端,它們仍然分配給以前的客戶端。
為了澄清,我不想使用“刪除孤立”選項 :從列表中刪除訂單時,不應從數據庫中刪除它,但應更新其客戶端分配(即,為null),如在Client#setOrders方法中定義。 怎么能這樣做?
編輯 :感謝我在這里收到的幫助,我能夠解決這個問題。 請參閱下面的解決方案
客戶端( “一個”/“擁有”一方 )存儲已在臨時字段中修改的訂單。
@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);
}
}
JPA沒有這樣做,據我所知,沒有JPA實現可以做到這一點。 JPA要求您管理關系的兩個方面。 當只更新關系的一側時,這有時被稱為“對象損壞”
JPA確實在雙向關系中定義了一個“擁有”方(對於OneToMany,這是沒有mappedBy注釋的一方),它用於在持久化到數據庫時解決沖突(只有一個表示數據庫中的關系與內存中的兩個相比,因此必須做出解決方案)。 這就是為什么要實現對ProductOrder類的更改,而不是對Client類的更改。
即使擁有“擁有”關系,您也應該始終更新雙方。 這通常會導致人們只依賴於更新一方,並且當他們打開二級緩存時會遇到麻煩。 在JPA中,只有當對象被持久化並從數據庫重新加載時,才會解決上述沖突。 一旦打開第二級緩存,可能會有幾筆交易在此期間,同時您將處理損壞的對象。
您還必須合並您刪除的訂單,僅合並客戶端是不夠的。
問題在於,雖然您要更改已刪除的訂單,但您永遠不會將這些訂單發送到服務器,也從不在它們上調用合並,因此您無法反映更改。
您需要在刪除的每個訂單上調用合並。 或者在本地處理更改,因此您無需序列化或合並任何對象。
EclipseLink確實具有雙向關系維護功能,在這種情況下可能對您有用,但它不是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回調,從而有效地從客戶端分離訂單。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.