db-jdo-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Craig L Russell <Craig.Russ...@Sun.COM>
Subject Relationships mapped with mapped-by (long)
Date Tue, 05 Sep 2006 20:29:09 GMT
Javadogs,

We have an issue with regard to the behavior of relationships mapped  
using mapped-by, where there is a bidirectional relationship  
instantiated by a single database artifact, either a foreign key or a  
row in a join table.

The spec currently defines a subset of behavior where relationships  
are updated by the program, and the memory model must be updated to  
reflect the change in the datastore after flush (including flush for  
commit).

The spec doesn't define the behavior when instances are deleted or  
primary keys are updated.

There are interacting metadata tags that affect the way the database  
schema is defined. Column attribute allows-null specifies whether the  
column is nullable or not. Foreign-key attribute delete-action  
specifies how the foreign key is defined in the database. Field  
attribute mapped-by specifies the field on the other side that shares  
a mapping artifact in the database. Field attribute null-value  
specifies the behavior of null values at flush time. Field attribute  
dependent specifies that there is a dependency relationship (if the  
instance is deleted, the instance referred to by the dependent field  
is also deleted).

The proposals below do not add any more metadata concepts, but reuse  
existing concepts. The proposals are intended for incorporation into  
the JDO 2 maintenance release.

Many-many relationships:

These are implemented as join tables, in which each row in the join  
table corresponds to an element (or key or value) in a field on each  
of the two sides. The join table is typically mapped on one side, and  
the field on the other side refers to the field with a mapped-by  
attribute. But the behavior is the same regardless of which side  
defines the mapping.

If either side adds an instance to the map or collection, at flush  
time a new row is added to the join table. The specification requires  
that for consistency, the other side's collection be updated in memory.

If either side removes an instance from the map or collection, at  
flush time the corresponding row is removed from the join table. The  
specification requires that for consistency, the other side's  
collection be updated in memory to remove the corresponding element  
or entry from the collection or map.

If an instance participating in a relationship is deleted, and the  
field referring to the other side is defined as dependent, then all  
instances of the other side are deleted, and all rows in the join  
table corresponding to the deleted instances are deleted.

If an instance participating in a relationship is deleted, and the  
field referring to the other side is not defined as dependent, at  
flush time the corresponding rows are removed from the join table. I  
believe that the specification requires that for consistency, all of  
the other side's collections be updated in memory to remove the  
corresponding element or entry from the collection or map. But I  
think it would be a good idea to add this explicitly to the  
specification.

One-many relationships:

The foreign key is typically specified on the "many" side, which  
declares a reference type; and the "one" side declares an element,  
key, or value, and uses the mapped-by attribute. The specification  
doesn't require the mapped-by attribute on the "one" side; it's just  
less work to define it there.

These relationships might also be mapped using a join table in which  
the existence of a relationship is indicated by a row in the join  
table. The semantics and behavior are the same as if the column  
defining the relationship is in the "many" side's mapping.

If the "many" side reference in instance X is updated (from instance  
A to instance B) then the spec requires that the underlying database  
column(s) be updated from referring to A to referring to B. The  
memory model is made consistent, by removing X from A's collection  
and adding X to B's collection.

If the "one" side of instance B adds instance X (which currently  
refers to A) to its collection, at flush time the specification  
requires that the underlying database column be updated in the row  
corresponding to instance X to refer to B. The memory model is made  
consistent, by removing X from A's collection and updating X to refer  
to B. A conflicting update is one in which some other instance of the  
"one" type also adds X to its collection, or instance X is changed so  
it refers to an instance of the "one" type other than B.

If the "one" side of instance A removes instance X (which currently  
refers to A) from its collection, and instance X has not been added  
to a different collection and its reference has not been changed to  
another instance, the spec is silent.

<proposed>
If the field in X is defined as null-value="exception", then at flush  
time this is an error. If the column that defines the relationship is  
defined as allows-null="false", then this is an error, as there is no  
known value to which the column can be changed. If the column is  
defined as allows-null="true" and the field in X is defined as null- 
value="none", then the column is updated to null and the memory model  
is changed so that the reference in X is null.

Deleting an instance X on the "many" side results in removing the  
instance from the database and updating the memory model to be  
consistent. If the field of instance A that contains X is  
instantiated in memory, then the collection is updated to remove X.

Deleting an instance A on the "one" side that contains an instance X  
is ok if instance X is also deleted in the same transaction. There  
are two cases:

o If the collection field is defined as dependent-element="true" then  
instance X is also deleted by the jdo implementation.

o If instance X is not also deleted by the program, and the  
collection field is defined with dependent-element="false" then  
instance X is not deleted. If the field in X is defined as null- 
value="exception", then at flush time this is an error, as the memory  
model cannot be made consistent. If the column that defines the  
relationship is defined as allows-null="false", then this is an  
error, as there is no known value to which the column can be changed.  
If the column is defined as allows-null="true", and the field in X is  
defined as null-value="none", then at flush time the column is  
updated to null and the memory model is changed so that the reference  
in X is null. NOTE: This behavior can be done automatically by the  
database if the foreign-key specifies delete-action="null".
</proposed>

One-one relationships:

These relationships are symmetric from the object model perspective,  
as currently the only metadata to describe whether one side or the  
other can exist independently is the null-value attribute of field.  
When mapped, typically the mapping is to a single unique foreign key  
column. This column is defined on one side of the relationship, and  
the mapping of the other side uses the mapped-by attribute. For the  
purposes of this discussion, I'll refer to the side containing the  
foreign key in its mapping as the mapped side, and the other as the  
mapped-by side.

If the mapped-by side reference of instance C is updated (from  
instance Y to instance Z) then at flush time the underlying database  
column(s) of Z is updated to refer to C. The specification requires  
that the memory model be made consistent, by changing Z to refer to  
C. Also, since there can only be one value in the database column  
containing C, the row corresponding to Y must be changed, but if Y  
has not also been updated to refer to another instance and no other  
instance has been updated to refer to Y, there is no value to which  
to set Y's reference except null.

<proposed>
If the field in Y is defined as null-value="exception", this is an  
error. If the field is defined as null-value="none", and the column  
defining the relationship is defined as allows-null="false", this is  
an error. If the field is defined as null-value="none", and the  
column defining the relationship is defined as allows-null="true",  
then the column is updated to null and the memory model is made  
consistent by setting the field in Y to null.
</proposed>

If the mapped side of instance Y is set to instance D (and Y   
currently refers to C), the spec requires that the underlying  
database column be updated in instance Y to refer to D. The memory  
model is made consistent, by setting the reference in D to Y and  
setting C's reference to null.

<proposed>
If the field in C is defined as null-value="exception", this is an  
error. If the field is defined as null-value="none", the column in  
the row corresponding to instance Y is updated to refer to D and  
there is no longer any row with a reference to C.
</proposed>

If the mapped-by side reference of instance C is deleted (and  
currently instance C refers to Y and vice versa) and the field in C  
is defined as dependent then at flush time both C and Y are deleted.

If the field in C is not defined as dependent and Y has not been  
changed to refer to another instance, then at flush time the  
reference in Y to C must be set to null.

<proposed>
If the field in Y is defined as null-value="exception", this is an  
error. If the field is defined as null-value="none", and the column  
defining the relationship is defined as allows-null="false", this is  
an error. If the field is defined as null-value="none", and the  
column defining the relationship is defined as allows-null="true",  
then the column in Y is updated to null and the memory model is made  
consistent by setting the field in Y to null.
</proposed>

If the mapped side reference of instance Y is deleted (and currently  
instance Y refers to C and vice versa) and the field in Y that refers  
to C is defined as dependent, then at flush time the rows  
corresponding to Y and C are deleted.

If the field is not defined as dependent, then at flush time the  
field in C must be set to null.

<proposed>
If the field in C is defined as null-value="exception", this is an  
error. If the field is defined as null-value="none", the row  
corresponding to instance Y is deleted and there is no instance that  
refers to C.
</proposed>

<spec>
The field on the other side of the relationship can be mapped by  
using the mapped-by at-
tribute identifying the field on the side that defines the mapping.  
Regardless of which side
changes the relationship, flush (whether done as part of commit or  
explicitly by the user)
will modify the datastore to reflect the change and will update the  
memory model for con-
sistency. There is no further behavior implied by having both sides  
of the relationship map
to the same database column(s). In particular, making a change to one  
side of the relation-
ship does not imply any runtime behavior by the JDO implementation to  
change the other
side of the relationship in memory prior to flush, and there is no  
requirement to load fields
affected by the change if they are not already loaded. This implies  
that if the RetainVal-
ues flag or DetachAllOnCommit is set to true, and the relationship  
field is loaded, then
the implementation will change the field on the other side so it is  
visible after transaction
completion.
Conflicting changes to relationships cause a JDOUserException to be  
thrown at flush
time. Conflicting changes include:
•adding a related instance with a single-valued mapped-by  
relationship field to
more than one one-to-many collection relationship
•setting both sides of a one-to-one relationship such that they do  
not refer to each
other
</spec>


Craig Russell
Architect, Sun Java Enterprise System http://java.sun.com/products/jdo
408 276-5638 mailto:Craig.Russell@sun.com
P.S. A good JDO? O, Gasp!


Mime
View raw message