Reputation: 547
I am using JPA to persist a bidirectional one to many relationship, a person can have many addresses. I intentionally do not use any cascade, I want to store every entity myself.
In my concrete use case I have an existing person entity and want to add an address. This happens in one facelet where users can not only add the address but change the attributes of the existing person as well. In the service layer I therefor call first a em.merge()
on the person and then an em.persist()
on the address. The problem is, that the address is going to be persisted two times.
While debugging I found out, that em.merge()
of the person not only updates the person but also creates the address, which is subsequently created again through the em.persist()
.
In my project I am using JSF, CDI & EJB on Glassfish server (with eclipselink) but I could reproduce the behaviour in a unit test with a RESOURCE_LOCAL
JPA. The appended example code produces the exact error I get on my JavaEE environment. I made te following observations:
Question is: Why is the to-many entity created on calling merge() on the to-one entity although I do not specify any cascade? I would be grateful if someone has a hint for me.
@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;
...
}
Upvotes: 0
Views: 754
Reputation: 11561
I get the correct response using Hibernate as the JPA provider. Since you mentioned you are testing under TopLink, I would suggest submitting a bug report in to 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
I didn't try to reproduce it under toplink, I assume your error is reproducible under that configuration.
Upvotes: 1