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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10634755/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 01:55:23  来源:igfitidea点击:

One-to-many relationship: Update removed children with JPA 2.0

javajpajpa-2.0entity-relationshipone-to-many

提问by SputNick

I have a bidirectional one-to-many relationship.

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

0 or 1 client<-> List of 0 or more product orders.

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

That relationship should be set or unset on both entities: On the client side, I want to set the List of product orders assigned to the client; the client should then be set / unset to the orders chosen automatically. On the product order side, I want to set the client to which the oder is assigned; that product order should then be removed from its previously assiged client's list and added to the new assigned client's list.

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

I want to use pure JPA 2.0 annotations and one "merge" call to the entity manager only (with cascade options). I've tried with the following code pieces, but it doesn't work (I use EclipseLink 2.2.0 as persistence provider)

我只想使用纯 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
}

Facade code for persisting client:

持久化客户端的外观代码:

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

Facade code for persisting product order:

持久化产品订单的外观代码:

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

When changing the client assignment on the order side, it works well: On the client side, the order gets removed from the previous client's list and is added to the new client's list (if re-assigned).

在订单端更改客户分配时,效果很好:在客户端,订单从前一个客户的列表中删除,并添加到新客户的列表中(如果重新分配)。

BUT when changing on the client side, I can only add orders(on the order side, assignment to the new client is performed), but it just ignores when I remove orders from the client's list (after saving and refreshing, they are still in the list on the client side, and on the order side, they are also still assigned to the previous client.

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

Just to clarify, I DO NOT want to use a "delete orphan" option: When removing an order from the list, it should not be deleted from the database, but its client assignment should be updated (that is, to null), as defined in the Client#setOrders method. How can this be archieved?

只是为了澄清,我不想使用“删除孤儿”选项:从列表中删除订单时,不应将其从数据库中删除,但应更新其客户端分配(即,为空),如在 Client#setOrders 方法中定义。如何存档?



EDIT: Thanks to the help I received here, I was able to fix this problem. See my solution below:

编辑:多亏了我在这里得到的帮助,我才能够解决这个问题。请参阅下面的我的解决方案:

The client ("One" / "owned" side) stores the orders that have been modified in a temporary field.

客户端(“One”/“owned”端)将修改过的订单存储在一个临时字段中。

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

On the facade, when persisting, it checks if there are any entities that must be persisted, too. Note that I used an interface to encapsulate this logic as my facade is actually generic.

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

// 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);
    }
}

回答by Pace

JPA does not do this and as far as I know there is no JPA implementation that does this either. JPA requires you to manage both sides of the relationship. When only one side of the relationship is updated this is sometimes referred to as "object corruption"

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

JPA does define an "owning" side in a two-way relationship (for a OneToMany this is the side that does NOT have the mappedBy annotation) which it uses to resolve a conflict when persisting to the database (there is only one representation of this relationship in the database compared to the two in memory so a resolution must be made). This is why changes to the ProductOrder class are realized but not changes to the Client class.

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

Even with the "owning" relationship you should always update both sides. This often leads people to relying on only updating one side and they get in trouble when they turn on the second-level cache. In JPA the conflicts mentioned above are only resolved when an object is persisted and reloaded from the database. Once the 2nd level cache is turned on that may be several transactions down the road and in the meantime you'll be dealing with a corrupted object.

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

回答by James

You have to also merge the Orders that you removed, just merging the Client is not enough.

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

The issue is that although you are changing the Orders that were removed, you are never sending these orders to the server, and never calling merge on them, so there is no way for you changes to be reflected.

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

You need to call merge on each Order that you remove. Or process your changes locally, so you don't need to serialize or merge any objects.

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

EclipseLink does have a bidirectional relationship maintenance feature which may work for you in this case, but it is not part of JPA.

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

回答by Jean

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

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

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.

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