I remembered to update the subject this time. So if I replace
arcSnapshot.put(property.getName(), target); with if (property.getRelationship().isUsedForLocking()) { arcSnapshot.put(property.getName(), target); } then the primary key qualifier is correct: "WHERE USER_ID = 2" instead of "WHERE USER_ID is null." Unfortunately, this also causes several unit tests to fail. I haven't yet investigated why this might be. Failed tests: testReadToOneRelationship(org.apache.cayenne.access.NestedDataContextReadTest) testRemoveToMany(org.apache.cayenne.CDOSetRelationshipTest) testRemove(org.apache.cayenne.CDOMany2OneTest) testNullifyToOne(org.apache.cayenne.access.NestedDataContextWriteTest) testMultipleToOneDeletion(org.apache.cayenne.unit.jira.CAY_901Test) testRemoveToMany(org.apache.cayenne.CDOMapRelationshipTest) testPhantomRelationshipModificationValidate(org.apache.cayenne.access.DataContextExtrasTest) testRemove1(org.apache.cayenne.CDOOne2ManyTest) testRemove2(org.apache.cayenne.CDOOne2ManyTest) testIsToOneTargetModified(org.apache.cayenne.access.DataRowUtilsTest) testRemoveToMany(org.apache.cayenne.CDOCollectionRelationshipTest) So far, basic functionality for my app seems working. On Fri, Sep 13, 2013 at 4:46 PM, Mike Kienenberger <mkien...@gmail.com> wrote: > 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/%3c8f985b960803271232s5018a5a9hbf0f731f82666...@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 <mkien...@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.