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 的回答和我的进一步调查,我们可以总结:
- EclipseLink 2.5.0 - 按预期工作。
- DataNucleus 4.1.9 - 按预期工作。
- Hibernate 5.1.0.Final, 5.0.3.Final 4.3.11.Final, 4.2.21.Final,... - 就是行不通。
我对 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())。
有人可以解释一下我遇到的以下奇怪行为吗?我正在尝试保留一些新的子对象并同时将它们添加到父集合中。最后,父集合中的元素数量是我预期的两倍。让我举个例子:
@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 的回答和我的进一步调查,我们可以总结:
- EclipseLink 2.5.0 - 按预期工作。
- DataNucleus 4.1.9 - 按预期工作。
- Hibernate 5.1.0.Final, 5.0.3.Final 4.3.11.Final, 4.2.21.Final,... - 就是行不通。
我对 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())。