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.