3

私は双方向の一対多の関係を持っています。

0 または 1クライアント<-> 0 個以上の製品注文のリスト。

その関係は、両方のエンティティで設定または設定解除する必要があります。クライアント側では、クライアントに割り当てられた製品注文のリストを設定したいと考えています。クライアントは、自動的に選択された注文に設定/設定解除する必要があります。製品注文側では、注文が割り当てられているクライアントを設定したい。その製品注文は、以前に割り当てられたクライアントのリストから削除され、新しい割り当てられたクライアントのリストに追加されます。

純粋な 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)

注文側でクライアントの割り当てを変更すると、うまく機能します。クライアント側では、注文が以前のクライアントのリストから削除され、新しいクライアントのリストに追加されます (再割り当てされた場合)。

しかし、クライアント側で変更する場合、注文を追加することしかできません(注文側では、新しいクライアントへの割り当てが実行されます)が、クライアントのリストから注文を削除すると無視されます(保存して更新した後、それらはまだ残っていますクライアント側のリスト、および注文側でも、以前のクライアントに割り当てられたままです。

明確にするために、「delete orphan」オプションを使用したくありません。リストから注文を削除する場合、データベースから削除するべきではありませんが、クライアントの割り当てを更新する必要があります (つまり、null に)。 Client#setOrders メソッドで定義されています。これはどのようにアーカイブできますか?


編集:ここで受けた助けのおかげで、この問題を解決できました。以下の私の解決策を参照してください。

クライアント ( 「One」/「所有」側) は、変更された注文を一時フィールドに保存します。

@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);
    }
}
4

3 に答える 3

5

JPAはこれを行いません。私が知る限り、これを行うJPA実装もありません。JPA では、関係の両側を管理する必要があります。関係の片側のみが更新される場合、これは「オブジェクトの破損」と呼ばれることがあります。

JPAは、データベースに永続化するときに競合を解決するために使用する双方向の関係で「所有」側を定義します(OneToManyの場合、これはmappedByアノテーションを持たない側です)(これの表現は1つだけです)データベース内のリレーションシップをメモリ内の 2 つと比較して、解決する必要があります)。これが、ProductOrder クラスへの変更は反映されますが、Client クラスへの変更は反映されない理由です。

「所有」関係であっても、常に両側を更新する必要があります。これにより、多くの場合、人々は片側のみの更新に依存するようになり、二次キャッシュをオンにすると問題が発生します。JPA では、上記の競合は、オブジェクトが永続化され、データベースから再ロードされたときにのみ解決されます。2 番目のレベルのキャッシュがオンになると、いくつかのトランザクションが進行する可能性があり、その間に破損したオブジェクトを処理することになります。

于 2012-05-17T13:16:51.017 に答える
0

Another possible solution is to add the new property on your ProductOrder, I named it detached in the following examples.

When you want to detach the order from the client you can use a callback on the order itself:

@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;
    }
  }
}

Instead of deleting the orders you want to delete you set detached to true. When you will merge & flush the client, the entity manager will detect the modified order and execute the @PreUpdate callback effectively detaching the order from the client.

于 2012-06-03T11:02:39.833 に答える
0

削除した注文もマージする必要があります。クライアントをマージするだけでは十分ではありません。

問題は、削除された注文を変更しているにもかかわらず、これらの注文をサーバーに送信したり、マージを呼び出したりしていないため、変更を反映する方法がないことです。

削除する注文ごとにマージを呼び出す必要があります。または、変更をローカルで処理して、オブジェクトをシリアル化またはマージする必要がないようにします。

EclipseLink には、この場合に役立つ双方向の関係維持機能がありますが、JPA の一部ではありません。

于 2012-05-17T13:23:44.133 に答える