Hi Craig -
I agree that the way I read th JPA standard this behavior is not disallowed.
FYI - The "replacement" happens at EnityManager.flush() and NOT at
EntityManager.persist(entity). The example demonstrates that very
situation.
What I am calling "implicit flush" was how I originally noticed the
behavior. OpenJPA was doing SQL inserts in the middle of the
transaction long before the commit (seen with SQL logging on) evidently
to populate the GenerationType.IDENTITY value needed for a foreign key
in another relationship on the same entity. In the example you can see
that the id field is set after the flush. When I noticed that you could
do the same thing by explicitly calling flush() I put that simpler case
into the example.
Since this behavior has a history, is there a design pattern for using
these collections that can become stale at some unpredictable time
during the transaction? Apparently the best, although not perfect
solution, is to always call getMembers() every time the collection is
referenced. This at least narrows the window of potential staleness.
In case it matters - this is with Postgres 8.2.13 and JDBC driver
postgresql-8.3-604.jdbc3.jar
- Paul
On 4/29/2009 8:56 AM, Craig L Russell wrote:
Hi Paul,
Thanks for following up on this.
On Apr 28, 2009, at 11:19 PM, Paul Copeland wrote:
Here is an OpenJPA 1.2.1 example that demonstrates the "OpenJPA
replaces a collection field" problem. This may not be technically a
bug but it can be a problem for the unaware user.
The JPA specification is silent on this issue.
However, the JDO specification requires that the collection field be
replaced at makePersistent (not flush). Since OpenJPA is based on
Kodo, which implemented JDO, this behavior is historic.
The reason for replacing the user's collection is to be able to track
changes made to the collection after makePersistent and before commit.
The reason to do it at makePersistent is so the user knows when it was
done.
This happens with a new persisted object with a collection field that
is initially null. If the null collection is initialized to an empty
collection it will correctly register additions until it is flushed.
Then the collection field is replaced and the old collection is stale.
In this example the flush is an explicit call. But the same problem
happens when the object is implicitly flushed to get the generated
identity for a foreign key.
This is more interesting. Does the implicit flush not happen at commit
or user flush?
Craig
In the main program the behavior changes when "em.flush()" is
commented out.
================= LazyLoadTest.java ==============
package com.jpatest;
import javax.persistence.*;
import java.util.*;
import com.jpatest.persistence.*;
public class LazyLoadTest
{
public static void main(String args[])
throws Throwable
{
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("jpatest");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ListHolder holder = new ListHolder();
em.persist(holder);
List<ListMember> listReference = holder.getMembers();
em.persist(new ListMember(holder));
System.err.println("id="+holder.id+"
size[expect=1]="+listReference.size());
em.persist(new ListMember(holder));
System.err.println("id="+holder.id+"
size[expect=2]="+listReference.size());
em.flush(); // **** this causes problem when uncommented
em.persist(new ListMember(holder));
System.err.println("id="+holder.id+"
size[expect=3]="+listReference.size());
em.getTransaction().commit();
em.close();
}
}
================= ListHolder.java ==============
package com.jpatest.persistence;
import java.util.*;
import javax.persistence.*;
@Entity
@Table (name="list_holder")
public class ListHolder
implements java.io.Serializable
{
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Id public long id;
@Version private long version;
@OneToMany(mappedBy="holder", fetch=FetchType.LAZY,
cascade={CascadeType.PERSIST,CascadeType.REMOVE})
private List<ListMember> members;
public ListHolder() {}
public List<ListMember> getMembers()
{
if (members == null) {
System.err.println("---- ListHolder members == null");
members = new ArrayList<ListMember>();
}
return members;
}
}
================= ListMember.java ==============
package com.jpatest.persistence;
import javax.persistence.*;
@Entity
@Table (name="list_member")
public class ListMember
implements java.io.Serializable
{
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Id private long id;
@Version private long version;
@ManyToOne
(optional=false,fetch=FetchType.LAZY,cascade=CascadeType.ALL)
private ListHolder holder;
protected ListMember() {}
public ListMember(ListHolder holder)
{
this.holder = holder;
holder.getMembers().add(this);
}
}
Craig L Russell
Architect, Sun Java Enterprise System http://db.apache.org/jdo
408 276-5638 mailto:[email protected]
P.S. A good JDO? O, Gasp!