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();
      }
   }

Reply via email to