When defining your objects to be persisted and the relationships between them, it is often required
to define dependencies between these related objects. What should happen when persisting an object
and it relates to another object? What should happen to a related object when an object is deleted?
You can define what happens with JDO2 and with DataNucleus. Let's take an example
public class Owner
{
private DrivingLicense license;
private Collection cars;
...
}
public class DrivingLicense
{
private String serialNumber;
...
}
public class Car
{
private String registrationNumber;
private Owner owner;
...
}
So we have an
Owner
of a collection of vintage
Car
's (1-N), and the
Owner
has a
DrivingLicense
(1-1). We want to define lifecycle dependencies to match the relationships
that we have between these objects. Firstly lets look at the basic Meta-Data for the objects.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo SYSTEM "file:/javax/jdo/jdo.dtd">
<jdo>
<package name="com.mydomain.samples.cars">
<class name="Owner">
<field name="license" persistence-modifier="persistent"/>
<field name="cars">
<collection element-type="com.mydomain.samples.cars.Car" mapped-by="owner"/>
</field>
</class>
<class name="DrivingLicense">
<field name="serialNumber"/>
</class>
<class name="Car">
<field name="registrationNumber"/>
<field name="owner" persistence-modifier="persistent"/>
</class>
</package>
</jdo>
JDO2 defines a concept called
persistence-by-reachability
. This means that when you
persist an object and it has a related persistable object then this other object is also
persisted. So using our example if we do
Owner bob = new Owner("Bob Smith");
DrivingLicense license = new DrivingLicense("011234BX4J");
bob.setLicense(license);
pm.makePersistent(bob); // "bob" knows about "license"
This results in both the
Owner
and the
DrivingLicense
objects being made
persistent since the
Owner
is passed to the PM operation and it has a field referring
to the unpersisted
DrivingLicense
object. So "reachability" will persist the
license.
With DataNucleus you can actually turn off
persistence-by-reachability
for particular
fields, by specifying in the MetaData a DataNucleus extension tag, as follows
<class name="Owner">
<field name="license" persistence-modifier="persistent">
<extension vendor-name="datanucleus" key="cascade-persist" value="false"/>
</field>
...
</class>
So with this specification when we call
makePersistent()
with an object of type
Owner
then the field "license" will not be persisted at that time.
As mentioned above JDO2 defines a concept called
persistence-by-reachability
. This
applies not just to persist but also to update of objects, so when you update an object and
its updated field has a persistable object then that will be persisted.
So using our example if we do
Owner bob = (Owner)pm.getObjectById(id);
DrivingLicense license2 = new DrivingLicense("233424BX4J");
bob.setLicense(license2); // "bob" knows about "license2"
So when this field is updated the new
DrivingLicense
object will be made persistent
since it is reachable from the persistent
Owner
object.
With DataNucleus you can actually turn off
update-by-reachability
for particular fields,
by specifying in the MetaData a DataNucleus extension tag, as follows
<class name="Owner">
<field name="license" persistence-modifier="persistent">
<extension vendor-name="datanucleus" key="cascade-update" value="false"/>
</field>
...
</class>
So with this specification when we call
makePersistent()
to update an object of type
Owner then the field "license" will not be updated at that time.
So we have an inverse 1-N relationship (no join table) between our
Owner
and his
precious
Car
's, and a 1-1 relationship between the
Owner
and his
DrivingLicense
, because without his license he wouldn't be able to drive the cars :-0.
What will happen to the
license
and the
cars
when the
owner
dies ? Well
in this particular case we want to define that the when the
owner
is deleted, then
his
license
will also be deleted (since it is for him only), but that his
cars
will continue to exist, because his daughter will inherit them.
In JDO2 this is called
Dependent Fields
.
To utilise this concept to achieve our end goal we change the Meta-Data to be
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo SYSTEM "file:/javax/jdo/jdo.dtd">
<jdo>
<package name="com.mydomain.samples.cars">
<class name="Owner">
<field name="license" persistence-modifier="persistent" dependent="true"/>
<field name="cars">
<collection element-type="com.mydomain.samples.cars.Car" mapped-by="owner"
dependent-element="false"/>
</field>
</class>
<class name="DrivingLicense">
<field name="serialNumber"/>
</class>
<class name="Car">
<field name="registrationNumber"/>
<field name="owner" persistence-modifier="persistent" dependent="false"/>
</class>
</package>
</jdo>
So it was as simple as just adding
dependent
and
dependent-element
attributes to
our related fields. Notice that we also added one to the other end of the Owner-Car relationship,
so that when a
Car
comes to the end of its life, the
Owner
will not die with it.
It may be the case that the owner dies driving the car and they both die at the same time,
but their deaths are independent!!
Just as we made use of
dependent-element
for collection fields, we also can make
use of
dependent-key
and
dependent-value
for map fields, and
dependent-element
for array fields.
Dependent Field
s is utilised in the following situations
-
An object is deleted (using
deletePersistent()
) and that object has relations to
other objects. If the other objects (either 1-1, 1-N, or M-N) are dependent then they are
also deleted.
-
An object has a 1-1 relation with another object, but the other object relation is nulled
out. If the other object is dependent then it is deleted when the relation is nulled.
-
An object has a 1-N collection relation with other objects and the element is removed from
the collection. If the element is dependent then it will be deleted when removed from the
collection. The same happens when the collections is cleared.
-
An object has a 1-N map relation with other objects and the key is removed from the map.
If the key or value are dependent and they are not present in the map more than once they
will be deleted when they are removed. The same happens when the map is cleared.
With JDO2 you can use "dependent-field" as shown above. As an alternative, when using RDBMS,
you can use the datastore-defined foreign keys and let the datastore built-in
"referential integrity" look after such deletions. DataNucleus provides a PMF property
datanucleus.deletionPolicy
allowing enabling of this mode of operation.
The default setting of
datanucleus.deletionPolicy
is "JDO2" which performs deletion of
related objects as follows
-
If
dependent-field
is true then use that to define the related objects to be
deleted.
-
Else, if the column of the foreign-key field is NULLable then NULL it and leave the
related object alone
-
Else deleted the related object (and throw exceptions if this fails for whatever
datastore-related reason)
The other setting of
datanucleus.deletionPolicy
is "DataNucleus" which performs
deletion of related objects as follows
-
If
dependent-field
is true then use that to define the related objects to be
deleted.
-
If a
foreign-key
is specified (in MetaData) for the relation field then leave any
deletion to the datastore to perform (or throw exceptions as necessary)
-
Else, if the column of the foreign-key field is NULLable then NULL it and leave the
related object alone
-
Else deleted the related object (and throw exceptions if this fails for whatever
datastore-related reason)
So, as you can see, with the second option you have the ability to utilise datastore "referential integrity"
checking using your MetaData-specified <foreign-key> elements.
One further complication is that with JDO there is also a process called
persistence-by-reachability at commit
. When objects are persisted, other objects are
persisted with them. If some relations are changed
before
commit and some of these
related objects are no longer required to be persistent then they will not be persisted.
For example, using our classes above
Owner bob = new Owner("Bob Smith");
DrivingLicense license = new DrivingLicense("233424BX4J");
bob.setLicense(license); // "bob" knows about "license"
pm.makePersistent(bob);
DrivingLicense license2 = new DrivingLicense("344566A99XH");
bob.setLicense(license2); // "bob" doesnt know about "license" now. It knows about "license2" now.
// "bob" and "license2" will be persisted but "license" wont be since not persisted explicitly
// and at commit it is no longer reachable from a persisted object
tx.commit();
With DataNucleus you can turn off
persistence-by-reachability at commit
by setting the
PersistenceManagerFactory
property
datanucleus.persistenceByReachabilityAtCommit
to false.