【问题标题】:One-to-many relationship: Update removed children with JPA 2.0一对多关系:使用 JPA 2.0 更新删除的子项
【发布时间】:2012-05-17 11:20:55
【问题描述】:

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

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)

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

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

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


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

客户端(“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;
}

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

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

【问题讨论】:

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


    【解决方案1】:

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

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

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

    【讨论】:

    • 感谢您对“拥有方”如何影响冲突解决的解释。现在我理解了这种行为。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 在这种情况下(甚至一般情况下),是否会在 Orders 帮助上添加级联(所以当合并客户端时,Order 会自动合并)?
    【解决方案3】:

    另一种可能的解决方案是在您的 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;
        }
      }
    }
    

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

    【讨论】:

    • 好主意。因为我其实是想删除订单,所以我用了类似的机制加上@PreRemove注解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-14
    • 1970-01-01
    • 2011-08-04
    • 1970-01-01
    • 2012-05-25
    相关资源
    最近更新 更多