Monday, September 24, 2007 Hi Esteve,
I've taken a look at the test case that you made available. I've reproduced the behavior that you are reporting. I can tell you where the problem lies, and I've attached code for you that does what your test case wants to do and works as expected.
So what's the problem in your test code?In JPA, the basic life cycle of entities involves four states: new, managed, detached, or removed. Entities become detached under the following conditions (from the JPA spec):
"3.2.4 Detached Entities"A detached entity may result from transaction commit if a transaction-scoped container-managed entity manager is used (see section 3.3); from transaction rollback (see section 3.3.2); from clearing the persistence context; from closing an entity manager; and from serializing an entity or otherwise passing an entity by value—e.g., to a separate application tier, through a remote interface, etc."
Therefore, closing the entity manager, when the persistence context has extended scope (the default for J2SE), causes the managed entities to become detached.
In your example, you create a new Parent in the runTest1 method, make it persistent (managed), and put it in a map. You then close the entity manager. Viola! The parent stored in the map under "OBJ" tag has become detached.
In the next method, runTest2, you take this Parent and add two new children to it. The two children are new. They are not managed. You merge this object keeping the original detached object in the map. You close the entity manager. Result, 2 new children added to the database, in a one-to-many relationship to the Parent. BUT, the Parent in the map, although still detached, still has two new (not detached nor managed) children.
In the next method, runTest3, you take the original Parent, and add two more new children to it. You then merge the detached Parent again. Result, four more new children added to the database, which the Parent has a one-to-many relationship to, but the the Parent "forgets" about the relationship to the two children (still in the database) added in runTest2.
The problem is that when you merge, you get back from the merge method, a reference to the resulting managed object. If you take that reference and put it in the map, you'll have after commit and the closing of the entity manager, a detached reference to a Parent with two detached (NOT NEW) children. Then in step three, when you add two more children, you end up with just four children in the database, with no forgetten children.
If you had put a version column in the Parent, I believe it would have caught the duplicate use of the same detached version of the Parent and thrown an OptimisticLockException in runTest3 since that version had already been merged and committed with changes once before in runTest2.
Make sense? Cheers, David Esteve Boix wrote:
David, I'm sorry to send a mail to your personal address... I've tried the mailing list, but no one replied: -------------------------------- I've been experiencing a problem with OpenJPA. Finally, I've been able to reproduce this outside my program, in an isolated test. You can download the test from: http://www.esteveb.com/OpenJPATest.zip (the mailing-list server keeps rejecting this mail as spam if I attach the file).This is an small description of what is this doing:Test1 method: Create a new parent object Store it in a map Persist it Test2 method: Retrieve parent from map Add 2 childs Update it to database Test3 method: Retrieve parent from map Add 2 childs more Update it to database After Test3, I end up with a child table with 6 childs. 2 from the first run (Test2), and 4 from the second run (Test3). Debugging the program with Netbeans, I see that OpenJPA doesn't update the id of the childs when I persist de parent, thus in the next run, it thinks that the childs are new (that's my guess). I'm using OpenJPA 1.0.0 on Java6, and Derby. Regards, Esteve -------------------------------- Could you run the test and tell me if you're observing this behaviour or am I doing something terribly wrong ? Best regards, Esteve En/na David Ezzio (asmtp) ha escrit:FYI -------- Original Message -------- Subject: [Fwd: Re: Update & OneToMany] Date: Mon, 13 Aug 2007 12:54:29 -0400 From: David Ezzio (asmtp) <[EMAIL PROTECTED]> To: [email protected] For some reason, the apache mail server mail server doesn't like this message. This is the 7th attempt. -------- Original Message -------- Subject: Re: Update & OneToMany Date: Fri, 10 Aug 2007 18:24:20 -0400 From: David Ezzio (asmtp) <[EMAIL PROTECTED]> To: [email protected] References: <[EMAIL PROTECTED]> Hi Esteve, My test case does not reproduce the behavior that you are reporting. For me, OpenJPA is working as expected, adding one new Address and one new MESSAGE_ADDRESS entry. See abbreviated log below. I've attached the zip of the Eclipse project. I suggest that you try reproducing what I'm reporting, then play with it to see whether it starts to do what you are reporting. Let us know what you find out. I did get one complaint on the @JoinColumn annotation which I removed. I also simplified names and added the getters and setters, and created a test case. Hope this helps, David 1125 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 5294604> executing prepstmnt 12985263 INSERT INTO Address (id, address) VALUES (?, ?) [params=(long) 954, (String) Route 66] 1140 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 5294604> executing prepstmnt 59219 INSERT INTO Address (id, address) VALUES (?, ?) [params=(long) 953, (String) Autobahn] 1140 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 5294604> executing prepstmnt 22221245 INSERT INTO Address (id, address) VALUES (?, ?) [params=(long) 952, (String) Route 1] 1140 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 5294604> executing prepstmnt 11644607 INSERT INTO Message (id, message, fromAddress_id) VALUES (?, ?, ?) [params=(long) 951, (String) Hello, (long) 953] 1140 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 5294604> executing prepstmnt 21465667 INSERT INTO MESSAGE_ADDRESSES (MESSAGE_ID, ADDRESS_ID) VALUES (?, ?) [params=(long) 951, (long) 952] 1140 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 5294604> executing prepstmnt 29774358 INSERT INTO MESSAGE_ADDRESSES (MESSAGE_ID, ADDRESS_ID) VALUES (?, ?) [params=(long) 951, (long) 954] 1171 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 13121387> executing prepstmnt 22878167 SELECT t0.id, t1.id, t1.address, t0.message FROM Message t0 LEFT OUTER JOIN Address t1 ON t0.fromAddress_id = t1.id 1171 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 13121387> executing prepstmnt 9045316 SELECT t1.id, t1.address FROM MESSAGE_ADDRESSES t0 INNER JOIN Address t1 ON t0.ADDRESS_ID = t1.id WHERE t0.MESSAGE_ID = ? [params=(long) 951] 1187 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 17535609> executing prepstmnt 20712275 INSERT INTO Address (id, address) VALUES (?, ?) [params=(long) 955, (String) Fleet Street] 1187 TRACE [main] openjpa.jdbc.SQL - <t 27263487, conn 17535609> executing prepstmnt 12413535 INSERT INTO MESSAGE_ADDRESSES (MESSAGE_ID, ADDRESS_ID) VALUES (?, ?) [params=(long) 951, (long) 955] Esteve Boix wrote:David, Thanks for your fast response. I'm attaching the simplified relevant entites (no getters/setters and no irrelevant fields). The TemporalMessage have a OneToOne mapping to an address (guess it, the "Mail from:" :) ) and a OneToMany mapping to the same table (the "Rcpt to:"). - I'm attaching the logs of what OpenJPA is doing. - I'm always accessing the collection (a List, actually) via the List interface, not using the setter to create a new List. I simply .add() new addresses and then I update the TemporalMessage. - The database is Derby in Client-Server mode. Best regards, Esteve PS: I'm attaching this as ZIP to try to circumvent the anti-ucm filter that keeps rejecting this mail... En/na David Ezzio ha escrit:Hi Esteve, Doesn't sound normal. Can you give us more info? Some questions that may help you understand what is going on. What are all the annotations for the OneToMany relationship? If Entity1 has a simple value attribute and you update that, does the same behavior occur for the association? Is it possible that you have assigned a new collection to your field, instead of modifying the existing collection? If so, does the behavior change if you change your application to modify the existing collection? HTH, David Ezzio Esteve Boix wrote:Hi all, I'm observing the following behaviour, and I'd like to know if it's normal or if it's a config problem: I have an Entity1 with a @OneToMany relation to Entity2, using a table to link both entities. Everytime I update Entity1, OpenJPA deletes all the links from the link table and recreates all the entities Entity2. This is weird cause I end up with a lot of orphaned Entity2 objects (OpenJPA clears the links, but not the original entities). Regards, Esteve
package harness;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import problem.model.*;
/**
* Original Test fixed up to work as expected
*/
public class Test2
{
private Map<String, Parent> map;
public Test2()
{
this.map = new HashMap<String, Parent>();
}
public void runTest1()
{
// get an EntityManagerFactory using the Persistence class; typically
// the factory is cached for easy repeated use
EntityManagerFactory factory =
Persistence.createEntityManagerFactory(null);
// get an EntityManager from the factory
EntityManager em = factory.createEntityManager();
// updates take place within transactions
EntityTransaction tx = em.getTransaction();
tx.begin();
Parent p1 = new Parent();
this.map.put("OBJ", p1);
// We DO NOT insert into childs.
em.persist(p1);
tx.commit();
em.close();
}
public void runTest2()
{
EntityManagerFactory factory =
Persistence.createEntityManagerFactory(null);
EntityManager em1 = factory.createEntityManager();
EntityTransaction tx1 = em1.getTransaction();
tx1.begin();
Parent p1 = this.map.get("OBJ");
p1.getChilds().add(new Child("Child1"));
p1.getChilds().add(new Child("Child2"));
Parent pManaged = em1.merge(p1);
this.map.put("OBJ", pManaged);
tx1.commit();
// this will detach pManaged
em1.close();
}
public void runTest3()
{
EntityManagerFactory factory =
Persistence.createEntityManagerFactory(null);
EntityManager em1 = factory.createEntityManager();
EntityTransaction tx1 = em1.getTransaction();
tx1.begin();
Parent p1 = this.map.get("OBJ");
p1.getChilds().add(new Child("Child3"));
p1.getChilds().add(new Child("Child4"));
Parent pManaged = em1.merge(p1);
this.map.put("OBJ", pManaged);
tx1.commit();
em1.close();
}
}
