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

时间:2012-05-17 11:20:55

标签: java jpa jpa-2.0 entity-relationship one-to-many

我有双向的一对多关系。

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

3 个答案:

答案 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回调,有效地从客户端分离订单。