Hibernate 在填充新持久化的子元素时复制父集合中的元素

Hibernate duplicates elements in parent collection while it is populating with newly persisted childs

有人可以解释一下我遇到的以下奇怪行为吗?我正在尝试保留一些新的子对象并同时将它们添加到父集合中。最后,父集合中的元素数量是我预期的两倍。让我举个例子:

@Entity
public class A {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Integer id;

    @OneToMany(mappedBy = "a")
    private List<B> bs = new ArrayList<>();

    public Integer getId() { return id; }

    public void setId(Integer id) { this.id = id; }

    public List<B> getBs() { return bs; }

    public void setBs(List<B> bs) { this.bs = bs; }
}

@Entity
public class B {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Integer id;

    @ManyToOne
    private A a;

    public Integer getId() { return id; }

    public void setId(Integer id) { this.id = id; }

    public A getA() { return a; }

    public void setA(A a) { this.a = a; }
}

测试用例:

@Test
public void test() throws Exception {
    entityManager.getTransaction().begin();

    A a = new A();

    entityManager.persist(a);
    entityManager.flush();
    entityManager.clear();

    a = entityManager.find(A.class, a.getId());

    for (int i = 0; i < 3; i++) {
        B b = new B();
        b.setA(a);
        entityManager.persist(b);
        a.getBs().add(b);
    }

    assertEquals(3, a.getBs().size());

    entityManager.getTransaction().commit();
}

结果:

java.lang.AssertionError: 
Expected :3
Actual   :6

我不问如何修改给定的代码以达到预期的结果。 我想了解为什么给定的代码会这样。


更新:适用于 EclipseLink 和 DataNucleus,但适用于 Hibernate。

感谢@riskop 的回答和我的进一步调查,我们可以总结:

我对 Hibernate 或 Spring 都不太熟悉,但应该

entityManager.flush();
entityManager.clear();

之后调用
entityManager.persist(b);

(可能除了你已经拥有的地方)

很遗憾,我不能确切地告诉你如何发生这种情况

但我会说这是一个 Hibernate 错误。应该 return 3.

我用 Hibernate 5.0 检查了这个。3.Final 遇到了同样的问题。然后我用 DataNucleus 4.1.9 检查了完全相同的代码,它按预期工作(没有重复的集合大小)。

但是,即使使用 Hibernate,如果您执行以下任一操作,问题也会消失:

  • 不使用@GeneratedValue(strategy = GenerationType.IDENTITY)。 GenerationType.IDENTITY 不鼓励,并且可能是其他问题的根源(只是 google )。如果将其更改为自动,则 Hibernate 可以正常工作。
  • 更改为实体 A 上的预取 (@OneToMany(mappedBy = "a", fetch=FetchType.EAGER)
  • 从数据库加载后初始化 A 上的集合(例如通过调用 a.getBs().size())。