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.
(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).
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!