You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cayenne.apache.org by Mike Kienenberger <mk...@gmail.com> on 2008/03/27 20:32:52 UTC

NullPointerException in ContextCommit with locking due to no retained snapshot on unset to-one relationship

Here's an interesting situation I'm debugging now for Cayenne 1.1.
It seems to be related to CAY-213 "NullPointerException in
ContextCommit with locking".  I suspect that it's true of 1.2 and
could very well be true for 3.0 as well, although I don't have that
handy to test with.

http://issues.apache.org/cayenne/browse/CAY-213

My testing seems to reveal that the same problem occurs when you set a
to-one relationship to null.  Line 291 in removeToManyTarget() sets
the state of the previous to-one relationship object to MODIFIED, but
doesn't retain a snapshot for that object.

Here's some simple test code that shows the problem.   And switching
the scalar setter with the relationship setter works around the
problem.

It seems to me that the the fix is to add

            dataContext.getObjectStore().retainSnapshot(this);

as was done for writeProperty().


    public void run() throws Exception
    {
        initCayenne("cayenne.xml");

        // Set up database
        createSchemaForObjEntityName(Configuration.getSharedConfiguration(),
"PotentialCustomer");
        DataContext dc = DataContext.createDataContext();

        // Set up test data
        PotentialCustomer pc =
(PotentialCustomer)dc.createAndRegisterNewObject(PotentialCustomer.class);
        Premise premise = (Premise)dc.createAndRegisterNewObject(Premise.class);
        pc.setToOneTarget("premise", premise, true);
        dc.commitChanges();

        // Force failure:
        pc.setToOneTarget("premise", null, true);
        premise.writeProperty("altitude", new Integer(0));

// On commitChanges(), no snapshot available for building locking
//      java.lang.NullPointerException
//          at org.objectstyle.cayenne.access.ContextCommit.appendOptimisticLockingAttributes(ContextCommit.java:564)

        dc.commitChanges();
    }


java.lang.NullPointerException
	at org.objectstyle.cayenne.access.ContextCommit.appendOptimisticLockingAttributes(ContextCommit.java:564)
	at org.objectstyle.cayenne.access.ContextCommit.prepareUpdateQueries(ContextCommit.java:426)
	at org.objectstyle.cayenne.access.ContextCommit.commit(ContextCommit.java:156)
	at org.objectstyle.cayenne.access.DataContext.commitChanges(DataContext.java:1266)
	at org.objectstyle.cayenne.access.DataContext.commitChanges(DataContext.java:1236)
	at com.gvea.cayenne.TestOptimisticLockingFailureOnSingleTargetNull.run(TestOptimisticLockingFailureOnSingleTargetNull.java:110)
	at com.gvea.cayenne.TestOptimisticLockingFailureOnSingleTargetNull.main(TestOptimisticLockingFailureOnSingleTargetNull.java:24)

Note that some of these line numbers may vary as my version of Cayenne
1.1 has local mods.

Re: NullPointerException in ContextCommit with locking due to no retained snapshot on unset to-one relationship

Posted by Mike Kienenberger <mk...@gmail.com>.
As I mentioned earlier, I'm upgrading my ancient Cayenne project from
1.1 to 3.x, currently 3.0.2.

I started by upgrading to 1.2 and 2.0, unfortunately hitting the old
null-relationship-breaks-optimistic-locking error.

http://mail-archives.apache.org/mod_mbox/cayenne-dev/200803.mbox/%3C8f985b960803271232s5018a5a9hbf0f731f82666e6a@mail.gmail.com%3E

Since most everything else seemed to be working, and the the
workaround I had for 1.1 wasn't possible in 1.2/2.0, I decided to skip
ahead to 3.0 and hope it was fixed there, or that it'd be more
relevant to fix there.

But the same behavior I see in 1.2 and 2.0 still occurs in 3.0.2.  For
1.1, the fix was to retain a new snapshot when resolving faults, but
the problem here seems to be slightly different.

My model has a "User" object and a "PotentialCustomer" object.  The
PotentialCustomer is an optional one-to-one relationship with the
User, where they both have the same primary key.  In the past I have
left the PotentialCustomer relationship as "Used for Locking",
although I've set it both ways without changing the resulting error.

Committing an unrelated attribute change to the "User" object when it
has no corresponding "PotentialCustomer" object generates a "where
USER_ID is null" clause.

Writing a property change eventually generates an  arcSnapshot for all
to-one relationships, even if they are not marked for locking.
org.apache.cayenne.access.ObjectDiff.java - line 114:

                public boolean visitToOne(ToOneProperty property) {

                    // eagerly resolve optimistically locked relationships
                    Object target = lock ?
property.readProperty(object) : property
                            .readPropertyDirectly(object);

                    if (target instanceof Persistent) {
                        target = ((Persistent) target).getObjectId();
                    }
                    // else - null || Fault

                    arcSnapshot.put(property.getName(), target);
                    return true;
                }

The problem is that with a relationship which is optional, the target
is going to be null.   And later on, when we generate optimistic
locking qualifiers in
org.apache.cayenne.access.DataNodeSyncQualifierDescriptor, we store
this null value as the matching value for the record's primary key.

To me, part of the fix would seem to be to not do anything if we're
not locking on this column.   Why do we need to resolve a relationship
or store a snapshot for a column not involved in optimistic locking?

Second, even if this column is involved with optimistic locking, it
should not be used as a replacement value for the modified object's
primary key.  It's probably a model error to specify a relationship
based on the modified object's primary key as a locking column.
However, I can correct this by removing the "Used for Locking" value.


On Thu, Mar 27, 2008 at 3:32 PM, Mike Kienenberger <mk...@gmail.com> wrote:
> Here's an interesting situation I'm debugging now for Cayenne 1.1.
> It seems to be related to CAY-213 "NullPointerException in
> ContextCommit with locking".  I suspect that it's true of 1.2 and
> could very well be true for 3.0 as well, although I don't have that
> handy to test with.
>
> http://issues.apache.org/cayenne/browse/CAY-213
>
> My testing seems to reveal that the same problem occurs when you set a
> to-one relationship to null.  Line 291 in removeToManyTarget() sets
> the state of the previous to-one relationship object to MODIFIED, but
> doesn't retain a snapshot for that object.
>
> Here's some simple test code that shows the problem.   And switching
> the scalar setter with the relationship setter works around the
> problem.
>
> It seems to me that the the fix is to add
>
>             dataContext.getObjectStore().retainSnapshot(this);
>
> as was done for writeProperty().
>
>
>     public void run() throws Exception
>     {
>         initCayenne("cayenne.xml");
>
>         // Set up database
>         createSchemaForObjEntityName(Configuration.getSharedConfiguration(),
> "PotentialCustomer");
>         DataContext dc = DataContext.createDataContext();
>
>         // Set up test data
>         PotentialCustomer pc =
> (PotentialCustomer)dc.createAndRegisterNewObject(PotentialCustomer.class);
>         Premise premise = (Premise)dc.createAndRegisterNewObject(Premise.class);
>         pc.setToOneTarget("premise", premise, true);
>         dc.commitChanges();
>
>         // Force failure:
>         pc.setToOneTarget("premise", null, true);
>         premise.writeProperty("altitude", new Integer(0));
>
> // On commitChanges(), no snapshot available for building locking
> //      java.lang.NullPointerException
> //          at org.objectstyle.cayenne.access.ContextCommit.appendOptimisticLockingAttributes(ContextCommit.java:564)
>
>         dc.commitChanges();
>     }
>
>
> java.lang.NullPointerException
>         at org.objectstyle.cayenne.access.ContextCommit.appendOptimisticLockingAttributes(ContextCommit.java:564)
>         at org.objectstyle.cayenne.access.ContextCommit.prepareUpdateQueries(ContextCommit.java:426)
>         at org.objectstyle.cayenne.access.ContextCommit.commit(ContextCommit.java:156)
>         at org.objectstyle.cayenne.access.DataContext.commitChanges(DataContext.java:1266)
>         at org.objectstyle.cayenne.access.DataContext.commitChanges(DataContext.java:1236)
>         at com.gvea.cayenne.TestOptimisticLockingFailureOnSingleTargetNull.run(TestOptimisticLockingFailureOnSingleTargetNull.java:110)
>         at com.gvea.cayenne.TestOptimisticLockingFailureOnSingleTargetNull.main(TestOptimisticLockingFailureOnSingleTargetNull.java:24)
>
> Note that some of these line numbers may vary as my version of Cayenne
> 1.1 has local mods.

Re: NullPointerException in ContextCommit with locking due to no retained snapshot on unset to-one relationship

Posted by Mike Kienenberger <mk...@gmail.com>.
Ok.  I tried to do some testing on this, and as far as I can tell,
it's only broken for 1.1, and not 1.2, 2.0, or 3.0.

So I'm just going to patch it locally, and noting the patch in this thread:

Index: E:/workspace311/cayenne-1.1.4/src/cayenne/java/org/objectstyle/cayenne/CayenneDataObject.java
===================================================================
--- cayenne-1.1.4/src/cayenne/java/org/objectstyle/cayenne/CayenneDataObject.java	(revision
543577)
+++ cayenne-1.1.4/src/cayenne/java/org/objectstyle/cayenne/CayenneDataObject.java	(working
copy)
@@ -289,6 +289,7 @@
         relList.remove(value);
         if (persistenceState == PersistenceState.COMMITTED) {
             persistenceState = PersistenceState.MODIFIED;
+            dataContext.getObjectStore().retainSnapshot(this);
         }

         if (value != null && setReverse) {




On 3/27/08, Mike Kienenberger <mk...@gmail.com> wrote:
> Here's an interesting situation I'm debugging now for Cayenne 1.1.
>  It seems to be related to CAY-213 "NullPointerException in
>  ContextCommit with locking".  I suspect that it's true of 1.2 and
>  could very well be true for 3.0 as well, although I don't have that
>  handy to test with.
>
>  http://issues.apache.org/cayenne/browse/CAY-213
>
>  My testing seems to reveal that the same problem occurs when you set a
>  to-one relationship to null.  Line 291 in removeToManyTarget() sets
>  the state of the previous to-one relationship object to MODIFIED, but
>  doesn't retain a snapshot for that object.
>
>  Here's some simple test code that shows the problem.   And switching
>  the scalar setter with the relationship setter works around the
>  problem.
>
>  It seems to me that the the fix is to add
>
>             dataContext.getObjectStore().retainSnapshot(this);
>
>  as was done for writeProperty().
>
>
>     public void run() throws Exception
>     {
>         initCayenne("cayenne.xml");
>
>         // Set up database
>         createSchemaForObjEntityName(Configuration.getSharedConfiguration(),
>  "PotentialCustomer");
>         DataContext dc = DataContext.createDataContext();
>
>         // Set up test data
>         PotentialCustomer pc =
>  (PotentialCustomer)dc.createAndRegisterNewObject(PotentialCustomer.class);
>         Premise premise = (Premise)dc.createAndRegisterNewObject(Premise.class);
>         pc.setToOneTarget("premise", premise, true);
>         dc.commitChanges();
>
>         // Force failure:
>         pc.setToOneTarget("premise", null, true);
>         premise.writeProperty("altitude", new Integer(0));
>
>  // On commitChanges(), no snapshot available for building locking
>  //      java.lang.NullPointerException
>  //          at org.objectstyle.cayenne.access.ContextCommit.appendOptimisticLockingAttributes(ContextCommit.java:564)
>
>         dc.commitChanges();
>     }
>
>
>  java.lang.NullPointerException
>         at org.objectstyle.cayenne.access.ContextCommit.appendOptimisticLockingAttributes(ContextCommit.java:564)
>         at org.objectstyle.cayenne.access.ContextCommit.prepareUpdateQueries(ContextCommit.java:426)
>         at org.objectstyle.cayenne.access.ContextCommit.commit(ContextCommit.java:156)
>         at org.objectstyle.cayenne.access.DataContext.commitChanges(DataContext.java:1266)
>         at org.objectstyle.cayenne.access.DataContext.commitChanges(DataContext.java:1236)
>         at com.gvea.cayenne.TestOptimisticLockingFailureOnSingleTargetNull.run(TestOptimisticLockingFailureOnSingleTargetNull.java:110)
>         at com.gvea.cayenne.TestOptimisticLockingFailureOnSingleTargetNull.main(TestOptimisticLockingFailureOnSingleTargetNull.java:24)
>
>  Note that some of these line numbers may vary as my version of Cayenne
>  1.1 has local mods.
>