JPA清除收集并添加新项目

时间:2014-07-13 15:17:03

标签: java hibernate jpa orm hibernate-mapping

我有一个我想要清除的@OneToMany集合(列表),并在同一个事务中添加新元素。

使用

collection.clear();
collection.add(new EntityB());

只需添加新实例,永远不会删除任何内容。我有orphanRemoval = true收集字段。

增加:

// Parent entity
@OneToMany(mappedBy = "product", orphanRemoval = true)
private List<Feature> features = new ArrayList<>();

// Child entity
@ManyToOne(cascade = CascadeType.ALL)
private Product product;

// Clear and add attempt
product.getFeatures().clear();

Feature feature = new Feature(product, ls);
product.getFeatures().add(feature);

5 个答案:

答案 0 :(得分:24)

您尝试仅清除双向关联的一侧。

所以而不是:

collection.clear();

正如this article中所述,尝试清除双方并且它应该有效:

for(Iterator<Feature> featureIterator = features.iterator(); 
    featureIterator.hasNext(); ) {
    Feature feature = featureIterator .next();
    feature.setProduct(null);
    featureIterator.remove();
}

此外,从@ManyToOne中移除级联并将其移至@OneToMany

注意独特的约束

但是,如果您有唯一约束,则此clear + add反模式将无效,因为INSERT操作在DELETE操作之前执行,如this article中所述。

正确的方法是检查哪些条目需要删除,然后删除它们。然后,添加新的,并更新已修改的那些。这就是你正确合并集合的方法。

答案 1 :(得分:3)

这似乎是许多Hibernate版本中的一个错误。我已经使用EclipseLink对它进行了测试,它在那里工作没有问题。

作为Hibernate中的解决方法(在Hibernate 4.3.6-Final中测试):删除Feature实体中的所有级联并添加CascadeType.PERSIST(或CascadeType.ALLProduct实体。

为了确保它不起作用,请尝试以下操作:

EntityManager em = ...//fetch the entitymanager. If a Container-managed transaction, you already got it injected
em.getTransaction().begin();//only if resource-local persistence unit. Otherwise if JTA: open the transaction the JTA-specific way (if that was not already done by the container)
Product product = em.find(Product.class, productId);
for (Feature crtFeature : product.getFeatures()) {
    if (!em.contains(crtFeature)) {
       throw new RuntimeException("Feature is not managed, so removeOrpahns cannot work");
    }
}
product.getFeatures().clear();

Feature feature = new Feature(product, ls);
em.persist(feature);//you need this, as there is no cascading from Product to Feature.
product.getFeatures().add(feature);

em.getTransaction().commit();//if you work with a resource-local persistence unit. Otherwise if JTA: commit the transaction the JTA-specific way (if that was not already done by the container)

答案 2 :(得分:3)

事实证明,实际的解决方案是使用@JoinColumn注释而不是mappedBy =“”参数。

答案 3 :(得分:0)

在第2.9节“实体关系”中,JPA 2.1规范说:

  

如果孤立的实体是分离的,新的或已删除的实体,   orphanRemoval的语义不适用。

在从集合中删除实体时,您确定您的实体是在持久性上下文中管理的吗?

您可以使用fetch=fetchType.EAGERfetch joins进行修复。或者(取决于您的使用案例),设置适当的cascade选项可能就足够了。

答案 4 :(得分:0)

我最近遇到了类似的问题。对我来说,问题是孤儿仍然是从另一个管理实体引用的,并且为该关系定义了PERSIST级联:

// Parent entity
@OneToMany(mappedBy = "product", orphanRemoval = true)
private List<Feature> features = new ArrayList<>();

// Child entity
@ManyToOne
private Product product;

@ManyToOne
private Description description;

// Another entity (let's say descriptions can be shared between features)
@OneToMany(mappedBy = "description", cascade = CascadeType.PERSIST)
private List<Feature> features = new ArrayList<>();

假设所有涉及的实体都被管理,即加载到持久性上下文中。现在我们做与OP相同的事情:

// Clear and add attempt
product.getFeatures().clear();

Feature feature = new Feature(product, ls);
product.getFeatures().add(feature);

哲学上,这里的问题是如果只从Product实体中删除Feature,而不是从Description实体中删除Feature,则对象模型会变得不一致。毕竟,您希望删除功能,但仍然可以从其他对象引用它。从技术上讲,会发生两个冲突的级联进入Feature实体,结果可能取决于它们的应用顺序。

由于功能已从产品中的集合中删除,因此应用孤立删除功能,并且功能实体在下一次刷新期间转换为“已移除”状态,如JPA 2.1规范(2.9)中所述。我强调了相关部分:

  

指定为OneToOne或OneToMany支持的关联使用   orphanRemoval选项。以下行为适用于   orphanRemoval生效:

     
      
  • 如果一个实体是该目标的   从关系中删除关系(通过设置   关系为null或从关系中删除实体   集合),删除操作将应用于实体   而成为孤儿。 在刷新时应用删除操作   操作即可。 orphanRemoval功能适用于实体   由其父实体私下“拥有”的。手提   否则,申请必须不依赖于特定的顺序   删除,并且不得重新分配已成为孤儿的实体   另一种关系或否则试图坚持。如果是实体   孤立的是一个分离的,新的或被移除的实体,它的语义   orphanRemoval不适用。
  •   

但是,仍然从Description实体引用相同的功能,该实体具有朝向功能级联的PERSIST。 JPA 2.1规范说明如下:

  

应用于实体X的刷新操作的语义如下   如下:

     
      
  • 如果X是托管实体,则会将其同步到   数据库中。

         
        
    • 对于来自X的关系引用的所有实体Y,if   与Y的关系已经用级联元素注释   value cascade = PERSIST或cascade = ALL,应用持久化操作   到Y。
    •   
  •   

因此,即使我们不在描述上调用em.persist(),此级联也会对Feature实体执行“持久”操作。当执行刷新以触发此持久级联时,足以管理描述。

这意味着我们正在按照规范告诉我们不应该做的 - 执行孤立删除并持久保存在同一个实体上。在Hibernate的实践中似乎发生的是两个操作依次应用。首先,删除操作使Feature实体转换为“已移除”状态,然后持久操作将删除的实体转换回托管实体。因此,不会从数据库中删除该功能。