On 10/19/2011 02:02 AM, Andrus Adamchik wrote:
Just coming of a few months (!) of low intensity debugging of a single problem 
that looked as if Cayenne resets a non-null to-one relationship to null. The FK 
would be not null in the DB, but all instances of a given object across all 
DataContexts in a given app would return null for that one relationship. Turned 
out this wasn't an obscure race condition, but rather the following scenario:

1. Assume Artist and Painting with painting having to one relationship called "artist", 
but also an ObjAttribute called "artistId"
2. For an existing Artist you may create a new painting, call "setArtist", but don't set 
the "artistId" attribute explicitly.
3. Commit - that creates a valid record with non-null PAINTING.ARTIST_ID in the db, but 
the object snapshot stored in DataRowStore suddenly has NULL for "ARTIST_ID" 
key.
4. From here all DataContexts that fault this painting from the shared cache will have 
NULL "artist" relationship, even though it is not null in the DB.

The actual behavior during (3 - commit) I think is less deterministic and 
depends on the relative order of traversal of entity properties in the 
ClassDescriptor. So theoretically there may have been a reverse situation when 
NULL was saved to the DB, but not-null value remained in the snapshot. In any 
event it is extremely confusing.

If we take a broader look at this problem, it is a case of redundant mapping 
(something in the DB layer is mapped multiple times in the object layer). There 
can be other cases with yet unknown sets of problems (e.g. multiple 
ObjRelationships over the same DbRelationship; flattened relationships, with a 
matching set of 1-step relationships; etc).

We only have a single case where redundant mapping is handled correctly and 
consistently - exposed PK (meaningful or not) . Even if the exposed PK is 
generated by Cayenne, we have a mechanism to update object property. So this is 
a case when we do it right, and don't compromise on user choices of mapping 
scenarios.

In case of exposed FK we have 2 options - (1) just add a Modeler validation to 
discourage this type of mapping (quick and easy) and (2) actually analyze the 
above and other possible scenarios when we need to synchronize relationship and 
attribute and write code to do that. #2 can be done as a post-commit pass over 
the objects to sync redundant mappings... Just not clear which one of the 
redundant mappings should be used to sync the others...

Thoughts?

I've ran into the problem myself when using non-synthetic keys. A warning in the modeler would be useful, but I wouldn't prohibit the behavior. The work around for my code was something like this:

public class Painting {
  @Override
  public void setArtist(Artist artist) {
    super.setArtist(artist);
    this.setArtistId(artist.getId());
  }
}

Reply via email to