我有一个我想要清除的@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);
答案 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.ALL
)Product
实体。
为了确保它不起作用,请尝试以下操作:
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.EAGER
或fetch 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实体转换为“已移除”状态,然后持久操作将删除的实体转换回托管实体。因此,不会从数据库中删除该功能。