簡體   English   中英

一對多關系:使用JPA 2.0更新刪除的子項

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM