cayenne-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Mike Kienenberger <>
Subject Re: NullPointerException in ContextCommit with locking due to no retained snapshot on unset to-one relationship
Date Fri, 13 Sep 2013 20:46:12 GMT
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.

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. - line 114:

                public boolean visitToOne(ToOneProperty property) {

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

                    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 <> 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.
> 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(
>         dc.commitChanges();
>     }
> java.lang.NullPointerException
>         at org.objectstyle.cayenne.access.ContextCommit.appendOptimisticLockingAttributes(
>         at org.objectstyle.cayenne.access.ContextCommit.prepareUpdateQueries(
>         at org.objectstyle.cayenne.access.ContextCommit.commit(
>         at org.objectstyle.cayenne.access.DataContext.commitChanges(
>         at org.objectstyle.cayenne.access.DataContext.commitChanges(
>         at
>         at com.gvea.cayenne.TestOptimisticLockingFailureOnSingleTargetNull.main(
> Note that some of these line numbers may vary as my version of Cayenne
> 1.1 has local mods.

View raw message