Hi Craig -
Do you mean a JIRA about the case where "OpenJPA will decide to replace
the collection"? I agree it is not a bug, just a potential issue for
the unwary. Would it be useful to create a small example and post it here?
- Paul
On 4/13/2009 7:41 PM, Craig L Russell wrote:
Hi Paul,
On Apr 13, 2009, at 5:15 PM, Paul Copeland wrote:
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.
This is worth a JIRA if only to clarify the code and the behavior that
the code exposes.
Craig
- 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!
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!