我有一个关于Hibernate 3.6.7和JPA 2.0的问题。
考虑以下实体(为简洁起见,省略了一些getter和setter):
@Entity
public class Parent {
@Id
@GeneratedValue
private int id;
@OneToMany(mappedBy="parent")
private List<Child> children = new LinkedList<Child>();
@Override
public boolean equals(Object obj) {
return id == ((Parent)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
@Entity
public class Child {
@Id
@GeneratedValue
private int id;
@ManyToOne
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
@Override
public boolean equals(Object obj) {
return id == ((Child)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
现在考虑这段代码:
// persist parent entity in a transaction
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Parent parent = new Parent();
em.persist(parent);
int id = parent.getId();
em.getTransaction().commit();
em.close();
// relate and persist child entity in a new transaction
em = emf.createEntityManager();
em.getTransaction().begin();
parent = em.find(Parent.class, id);
// *: parent.getChildren().size();
Child child = new Child();
child.setParent(parent);
parent.getChildren().add(child);
em.persist(child);
System.out.println(parent.getChildren()); // -> [Child@1, Child@1]
em.getTransaction().commit();
em.close();
错误地将子实体插入父实体的子列表中两次。
执行以下操作之一时,代码正常工作(列表中没有重复的条目):
mappedBy
属性*
标记的取消注释行)这显然是一种非常奇怪的行为。此外,当使用EclipseLink作为持久性提供程序时,代码的工作方式与预期一致(没有重复)。
这是一个Hibernate错误还是我错过了什么?
由于
答案 0 :(得分:27)
这是Hibernate中的一个错误。令人惊讶的是,它还没有报道,feel free to report it。
对非初始化的延迟集合的操作进行排队,以便在初始化集合后执行它们,并且当这些操作与数据库中的数据冲突时,Hibernate不会处理这种情况。通常这不是问题,因为此队列在flush()
时被清除,并且可能的冲突更改也会在flush()
时传播到数据库。但是,一些更改(例如,由IDENTITY
类型的生成器生成的具有id的实体的持久化,我猜,这是你的情况)被传播到没有完整flush()
的数据库,并且在这些情况下冲突是可能的。
作为一种解决方法,您可以在坚持孩子之后flush()
会话:
em.persist(child);
em.flush();
答案 1 :(得分:2)
我通过告诉Hibernate不要在我的集合中添加重复项来解决这个问题。在您的情况下,将children
字段的类型从List<Child>
更改为Set<Child>
,并在equals(Object obj)
类上实施hashCode()
和Child
。
显然这在每种情况下都是不可能的,但如果有一种明智的方法来确定Child
实例是唯一的,那么这个解决方案可能相对无痛。
答案 2 :(得分:2)
当我遇到的问题不是将项目添加到使用@OneToMany注释的列表中时,我遇到了这个问题,但是在尝试迭代这样一个列表的项目时。列表中的项目总是重复,有时甚至超过两次。 (当用@ManyToMany注释时也发生了)。使用Set不是解决方案,因为这些列表应该允许其中包含重复元素。
示例:
@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
@Cascade(CascadeType.ALL)
@LazyCollection(LazyCollectionOption.FALSE)
private List entities;
事实证明,Hibernate使用left outer join
执行sql语句,这可能导致db返回重复的结果。使用OrderColumn:
@OrderColumn(name = "columnName")
答案 3 :(得分:1)
通过在Wildfly(8.2.0-Final)中使用Java企业上下文(我认为它的Hibernate版本4.3.7),我的解决方法是,首先坚持孩子将其添加到懒惰的集合中:
...
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void test(){
Child child = new Child();
child.setParent(parent);
childFacade.create(child);
parent.getChildren().add(cild);
parentFacade.edit(parent);
}
答案 4 :(得分:-1)
通过调用empty()方法来管理它。对于这种情况,
parent.getChildren().isEmpty()
之前
parent.getChildren().add(child);