JPA一对多关系创建实体两次
JPA one-to-many relationship creates entity twice
我正在使用 JPA 来保持双向一对多关系,一个人可以有多个地址。我故意不使用任何级联,我想自己存储每个实体。
在我的具体用例中,我有一个现有的个人实体并想添加一个地址。这发生在一个 facelet 中,用户不仅可以添加地址,还可以更改现有人员的属性。因此,在服务层中,我首先对人调用 em.merge()
,然后对地址调用 em.persist()
。问题是,该地址将被保留两次。
在调试时我发现,那个人的 em.merge()
不仅更新了这个人而且创建了地址,随后通过 em.persist()
.
再次创建了地址
在我的项目中,我在 Glassfish 服务器(使用 eclipselink)上使用 JSF、CDI 和 EJB,但我可以使用 RESOURCE_LOCAL
JPA 在单元测试中重现该行为。附加的示例代码产生了我在 JavaEE 环境中遇到的确切错误。我做了以下观察:
- 如果我在第 4 步之后执行第 3 步,一切正常
- 如果我在同一个事务中执行第 3 步和第 4 步,也一切正常
问题是:为什么在一对一实体上调用 merge() 时创建了对多实体,尽管我没有指定任何级联?如果有人给我提示,我将不胜感激。
@Test
public void test_OK_OnlyPersistAddressDirect()
{
// use case based on a existing person entity
em.getTransaction().begin();
Person p = new Person();
p.setId(1L);
p.setName("tom");
p.setAddresses(new ArrayList<Address>());
em.persist(p);
em.getTransaction().commit();
em.clear();
// 1) load the existing entity
Person persistedPerson = findPerson(1L);
persistedPerson.setName("lisa");
// 2) create a new address and link the two entities together
Address a = new Address();
a.setCity("paris");
a.setPerson(persistedPerson);
persistedPerson.getAddresses().add(a);
// 3) do a merge on the existing person as its attributes might have changed and its list of addresses has enlarged
em.getTransaction().begin();
em.merge(persistedPerson);
em.getTransaction().commit();
// 4) do a persist on the address as it should be persisted in the database
em.getTransaction().begin();
em.persist(a);
em.getTransaction().commit();
// 5) check whether the person entity has changed
List<Person> persons = em.createQuery("select p from Person p", Person.class).getResultList();
Assert.assertEquals(1, persons.size());
Assert.assertEquals("lisa", persons.get(0).getName());
Assert.assertEquals(1, persons.get(0).getAddresses().size());
// 6) check whether the address has been persisted
List<Address> addresses = em.createQuery("select a from Address a", Address.class).getResultList();
// HERE IS THE ERROR: expected:<1> but was:<2>
Assert.assertEquals(1, addresses.size());
}
@Entity
public class Person implements Serializable
{
@OneToMany(mappedBy = "person")
private List<Address> addresses;
...
}
@Entity
public class Address implements Serializable
{
@ManyToOne
@JoinColumn(name = "PERSON_ID", nullable = false)
private Person person;
...
}
我使用 Hibernate 作为 JPA 提供程序得到了正确的响应。由于您提到您正在 TopLink 下进行测试,我建议您向 TopLink 提交错误报告。
Hibernate: insert into Person (name, id) values (?, ?)
Hibernate: select person0_.id as id1_1_0_, person0_.name as name2_1_0_ from Person person0_ where person0_.id=?
Hibernate: update Person set name=? where id=?
Hibernate: insert into Address (id, city, PERSON_ID) values (default, ?, ?)
Hibernate: select person0_.id as id1_1_, person0_.name as name2_1_ from Person person0_
[model.Person@133b7b68]
Hibernate: select address0_.id as id1_0_, address0_.city as city2_0_, address0_.PERSON_ID as PERSON_I3_0_ from Address address0_
-->> output of addresses.size() = 1
我没有尝试在 toplink 下重现它,我假设你的错误在那个配置下是可以重现的。
我正在使用 JPA 来保持双向一对多关系,一个人可以有多个地址。我故意不使用任何级联,我想自己存储每个实体。
在我的具体用例中,我有一个现有的个人实体并想添加一个地址。这发生在一个 facelet 中,用户不仅可以添加地址,还可以更改现有人员的属性。因此,在服务层中,我首先对人调用 em.merge()
,然后对地址调用 em.persist()
。问题是,该地址将被保留两次。
在调试时我发现,那个人的 em.merge()
不仅更新了这个人而且创建了地址,随后通过 em.persist()
.
在我的项目中,我在 Glassfish 服务器(使用 eclipselink)上使用 JSF、CDI 和 EJB,但我可以使用 RESOURCE_LOCAL
JPA 在单元测试中重现该行为。附加的示例代码产生了我在 JavaEE 环境中遇到的确切错误。我做了以下观察:
- 如果我在第 4 步之后执行第 3 步,一切正常
- 如果我在同一个事务中执行第 3 步和第 4 步,也一切正常
问题是:为什么在一对一实体上调用 merge() 时创建了对多实体,尽管我没有指定任何级联?如果有人给我提示,我将不胜感激。
@Test
public void test_OK_OnlyPersistAddressDirect()
{
// use case based on a existing person entity
em.getTransaction().begin();
Person p = new Person();
p.setId(1L);
p.setName("tom");
p.setAddresses(new ArrayList<Address>());
em.persist(p);
em.getTransaction().commit();
em.clear();
// 1) load the existing entity
Person persistedPerson = findPerson(1L);
persistedPerson.setName("lisa");
// 2) create a new address and link the two entities together
Address a = new Address();
a.setCity("paris");
a.setPerson(persistedPerson);
persistedPerson.getAddresses().add(a);
// 3) do a merge on the existing person as its attributes might have changed and its list of addresses has enlarged
em.getTransaction().begin();
em.merge(persistedPerson);
em.getTransaction().commit();
// 4) do a persist on the address as it should be persisted in the database
em.getTransaction().begin();
em.persist(a);
em.getTransaction().commit();
// 5) check whether the person entity has changed
List<Person> persons = em.createQuery("select p from Person p", Person.class).getResultList();
Assert.assertEquals(1, persons.size());
Assert.assertEquals("lisa", persons.get(0).getName());
Assert.assertEquals(1, persons.get(0).getAddresses().size());
// 6) check whether the address has been persisted
List<Address> addresses = em.createQuery("select a from Address a", Address.class).getResultList();
// HERE IS THE ERROR: expected:<1> but was:<2>
Assert.assertEquals(1, addresses.size());
}
@Entity
public class Person implements Serializable
{
@OneToMany(mappedBy = "person")
private List<Address> addresses;
...
}
@Entity
public class Address implements Serializable
{
@ManyToOne
@JoinColumn(name = "PERSON_ID", nullable = false)
private Person person;
...
}
我使用 Hibernate 作为 JPA 提供程序得到了正确的响应。由于您提到您正在 TopLink 下进行测试,我建议您向 TopLink 提交错误报告。
Hibernate: insert into Person (name, id) values (?, ?)
Hibernate: select person0_.id as id1_1_0_, person0_.name as name2_1_0_ from Person person0_ where person0_.id=?
Hibernate: update Person set name=? where id=?
Hibernate: insert into Address (id, city, PERSON_ID) values (default, ?, ?)
Hibernate: select person0_.id as id1_1_, person0_.name as name2_1_ from Person person0_
[model.Person@133b7b68]
Hibernate: select address0_.id as id1_0_, address0_.city as city2_0_, address0_.PERSON_ID as PERSON_I3_0_ from Address address0_
-->> output of addresses.size() = 1
我没有尝试在 toplink 下重现它,我假设你的错误在那个配置下是可以重现的。