Craig - Thanks for the responses. This confirms that for a new Entity a
collection field may be null unless the application initializes it.
When you say "flushed" does that include calling EntityManager.flush()
before the transaction is committed? The spec says the field can be
null until it is "fetched". My expectation is that the field may remain
null even after calling EntityManager.flush().
The surprising thing is that when you add elements to such an
application initialized empty collection, in some situations OpenJPA
will decide to replace the collection. At that point if you are holding
a reference to the value returned by getMyPcList() that collection will
then be stale, possibly leading to inconsistent results for the caller.
- Paul
(other comments below)
On 4/13/2009 4:18 PM, Craig L Russell wrote:
Hi Paul,
On Apr 13, 2009, at 9:04 AM, Paul Copeland wrote:
Are there any responses from the OpenJPA experts on my two assertions
below? If the assertions seem wrong I will put together examples
that demonstrate the behavior. If the assertions are correct that is
not necessary.
From JPA Spec Section 2.1.7 - "If there are no associated entities
for a multi-valued relationship of an entity fetched from the database,
the persistence provider is responsible for returning an empty
collection as the value of the relationship."
Note the words "fetched from the database". My reading of this is
that if the Entity is new and has not been flushed to the database
(even though persist() has been called) the value could be null
rather than an empty collection. So the behavior of OpenJPA returning
null (assertion #1) would be consistent with the spec.
That's how I read it as well. Until the new entity is flushed, there's
no reason to have the entity manager provider messing with the values.
- Paul
On 4/9/2009 12:22 PM, Paul Copeland wrote:
Thanks for the assistance Craig -
Here are two assertions that I have observed in my testing with
OpenJPA 1.2.1 -
(1) A Field Access persistent collection has a null value when the
field is accessed if the collection is empty. This is the state of
the field in the transaction after the entity is first persisted
before the transaction is committed (these are the conditions that
occur in my process). Corollary - the null field is NOT
automatically changed to an empty Collection when first accessed. A
method returning the collection field will return null.
This is discussed above. The entity is not "fetched" but rather newly
persisted.
(2) The value of a null collection field (state as in #1 above) that
has been assigned to an initialized non-null value may be
automatically replaced before the transaction is committed at which
point references to the assigned value will be stale and no longer
updated (for instance when entity's are added to the collection).
This is discussed above. Until flush, any user changes to the
collection should be reflected in the database.
But one other thing to consider. It's the application's responsibility
to manage both sides of a relationship to be consistent at commit. So
if you're looking to update only the other side of a relationship
you're in trouble unless you use some OpenJPA special techniques.
Good point about updating both sides of the relation. In this case I am
using the OpenJPA API to detect if the other side has not been loaded
yet and only updating the other side when necessary. This is to avoid
loading a potentially very large collection that is not going to be used
during the life of that EntityManager. If and when the other side is
loaded OpenJPA will include the new elements then.
This does not change the question about null or empty collections however.
Craig
If the experts believe either of these assertions are incorrect then
I definitely want to investigate further.
- Paul
(further comments below)
On 4/9/2009 11:13 AM, Craig L Russell wrote:
Hi Paul,
On Apr 9, 2009, at 9:40 AM, Paul Copeland wrote:
Couple of clarifications -
A lazily loaded FIELD ACCESS collection is a null value when
initially accessed if the Collection is EMPTY (I said "null"
incorrectly below).
My comment below was intended to compare your "if null then
initialize" paradigm with my "initialize to an empty collection
during construction". So if the first time you access the
collection it is null your code sets the value to an empty
collection. My recommended code would never encounter a null
collection.
Your way works (as do other ways). :-)
The test I have shows this behavior for a newly persisted Entity
during the same transaction where em.persist(entity) is called.
This is with a LAZY loaded collection.
During persist, the provider should not replace fields. Replacing
fields behavior should happen at commit (flush) time. So if you
never explicitly initialize a field, it should have its Java
default value until flush.
This is NOT what I am seeing. In fact the replacement happens
during the transaction under certain conditions where the proxy is
apparently created during the transaction some time after the call
to em.persist(entity) and before commit.
If you're talking about wrapping the persistent collection with an
unmodifiable collection then you're talking about adding more
objects. I thought you were trying to avoid any object construction?
I would construct the unmodifiable collection (if the idiom worked)
only if and when the value is accessed and has already been loaded.
Other things being equal, I don't want to construct tens of
thousands of Collections in a tight loop that are never used. Given
database latencies it is a small point in overall performance. As I
said, there are good arguments either way and your recommendation is
one reasonable approach, but apparently not a JPA requirement.
In some applications there is a difference between an empty
collection and a null collection. There are properties that allow
that behavior to be implemented as well, although that's
non-standard and a bit more complicated.
It might be easier to look at a test case because I think we're
talking past each other.
Craig
On 4/9/2009 9:26 AM, Paul Copeland wrote:
Hi Craig -
My experience is not what you are describing. A lazily loaded
FIELD ACCESS collection is a null value when initially accessed
if the Collection is null (possibly a PROPERTY ACCESS collection
behaves differently as mentioned by Pinaki , I haven't tested that).
To repeat what is below -
getMyPcList()
returns null if the Collection is empty unless you initialize the
value with "new ArrayList()". This is what my testing shows with
1.2.1 - I wish it weren't this way since that might it make it
possible to use the Collections.unmodifiedList() idiom (as it is
that idiom has unreliable behavior). If the experts are pretty
sure that I am wrong about this then I definitely want to
investigate it further. I'd like to hear more.
I don't think you have given a reason to require initializing the
Collection at construction time or at first access -- there are
reasonable aesthetic and performance arguments either way.
- Paul
On 4/9/2009 7:01 AM, Craig L Russell wrote:
Hi Paul,
I like to think of entities as POJOs first, so I can test them
without requiring them to be persistent. So if you want code to
be able to add elements to collections, the collections must not
be null.
If you construct the field as null and then "lazily" instantiate
an empty collection, then anyway you end up with an empty
collection the first time you access the field. And constructing
an empty collection should not be even a blip on your
performance metric.
Considering everything, I still recommend that you instantiate
an empty collection when you construct an entity.
Craig
On Apr 8, 2009, at 10:21 AM, Paul Copeland wrote:
Pinaki -
I tried your suggestion of not initializing the value of
myPcList and I get a null pointer exception when adding to an
empty list.
I noticed your example was for Property access and Russell (and
I) were talking about Field access. Do you agree that it is
necessary to initialize an empty list when using Field access?
On Craig's advice to always construct a new ArrayList(), why is
that necessary instead of just constructing it in the getter
when it tests to null? Otherwise you are constructing an
ArrayList that is unnecessary when the List is NOT empty
(usually) and also unnecessary in the case of LAZY loading if
the List is never accessed (perhaps also a frequent case). In
some applications you might create lots of these objects and
normal optimization is to avoid calling constructors
unnecessarily. Just want to be clear about whether it is
necessary.
- Paul
On 4/8/2009 9:43 AM, Paul Copeland wrote:
Thanks Pinaki -
I think you are saying that at some point the proxy object
does replace the local List. Is that right?
I have seen that model - if (myPcList == null) myPcList = new
ArrayList() - in various examples (not sure where now).
Thanks for clearing that up. But then Craig Russell
contradicts you in his reply (below) where he recommends
always initializing the Collection in the constructor (which
seems like a performance anti-pattern of wasted constructor
calls since usually it will be replaced by the proxy). Are
you and Craig saying opposite things here?
In my testing when the List is empty - (myPcList == null) -
does indeed evaluate to true.
getMyPcList().add(new MyPcObject())
Therefore I thought the above would cause a null pointer
exception when the List is empty. You say that won't happen
so I'll give it a try!
- Paul
On 4/8/2009 3:16 AM, Pinaki Poddar wrote:
Hi,
According to JPA spec:
"If there are no associated entities for a multi-valued
relationship of an entity fetched from the database,
the persistence provider is responsible for returning an
empty collection as the value of the relationship."
That is what OpenJPA does. So the application do not need to
return an empty list for a null (initialized) list.
OpenJPA proxies all returned collections. So application code
can simply do the following
// In the domain class
private List<MyPcObject> myPcList = null; // never explictly
initialized
@OneToMany (mappedBy="ownerSide", fetch=FetchType.LAZY,
cascade=CascadeType.PERSIST)
public List<Promotion> getMyPcList() {
return myPcList; // return as it is
}
// In the application
List<Promotion> list = owner.getMyPcList();
assertNotNull(list);
assertTrue(java.util.List.class.isInstance(list));
assertNotSame(java.util.ArrayList.class, list.getClass());
list.add(new MyPcObject());
owner.setMyPcList(list);
On Apr 7, 2009, at 11:10 PM, Paul Copeland wrote:
Can OpenJPA replace a Collection when it is loaded?
With the code below when the list is initially empty you
need to create a List (ArrayList) so you can add elements
to it. When I persisted new objects on the ManyToOne side
and added them to the List that worked. But the first time
the List was loaded it seemed to replace my ArrayList with
the newly loaded data and made an older reference to the
ArrayList stale (no longer updated when more elements were
added to myPcList). This was all in one transaction.
So now I wonder if the initial null List is a special case
or if OpenJPA might replace the Collection anytime it
decides to load it again. Anyone know the answer?
If the list is persistent and the class is enhanced, the
collection will always reflect what's in the database.
If I don't create an initial ArrayList how can I add
elements when the List is empty?
I'd recommend always having a non-empty list. Initialize it
in the constructor to an empty list and don't check it after
that.
Here's what it would look like:
@OneToMany (mappedBy="ownerSide", fetch=FetchType.LAZY,
cascade=CascadeType.PERSIST)
private List<MyPcObject> myPcList = new
ArrayList<MyPcObject>();
List<Promotion> getMyPcList()
{
return myPcList;
}
Craig
Craig L Russell
Architect, Sun Java Enterprise System http://db.apache.org/jdo
408 276-5638 mailto:craig.russ...@sun.com
P.S. A good JDO? O, Gasp!
-----
Pinaki Poddar http://ppoddar.blogspot.com/
http://www.linkedin.com/in/pinakipoddar
OpenJPA PMC Member/Committer
JPA Expert Group Member
Craig L Russell
Architect, Sun Java Enterprise System http://db.apache.org/jdo
408 276-5638 mailto:craig.russ...@sun.com
P.S. A good JDO? O, Gasp!
Craig L Russell
Architect, Sun Java Enterprise System http://db.apache.org/jdo
408 276-5638 mailto:craig.russ...@sun.com
P.S. A good JDO? O, Gasp!
Craig L Russell
Architect, Sun Java Enterprise System http://db.apache.org/jdo
408 276-5638 mailto:craig.russ...@sun.com
P.S. A good JDO? O, Gasp!