Hibernate将重复项插入到@OneToMany集合中

时间:2011-10-26 14:04:57

标签: hibernate jpa-2.0 one-to-many

我有一个关于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错误还是我错过了什么?

由于

5 个答案:

答案 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);